home *** CD-ROM | disk | FTP | other *** search
/ PC/CD Gamer UK 120 / CD Gamer Issue 120 (March 2003) (Disc 2).ISO / mods / Q2_Codered / codeRED1_0.exe / Data1.cab / g_ctf.c < prev    next >
Encoding:
C/C++ Source or Header  |  2002-08-13  |  109.5 KB  |  4,198 lines

  1. /*
  2. Copyright (C) 1997-2001 Id Software, Inc.
  3.  
  4. This program is free software; you can redistribute it and/or
  5. modify it under the terms of the GNU General Public License
  6. as published by the Free Software Foundation; either version 2
  7. of the License, or (at your option) any later version.
  8.  
  9. This program is distributed in the hope that it will be useful,
  10. but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  
  12.  
  13. See the GNU General Public License for more details.
  14.  
  15. You should have received a copy of the GNU General Public License
  16. along with this program; if not, write to the Free Software
  17. Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
  18.  
  19. */
  20. #include "g_local.h"
  21. #include "m_player.h"
  22.  
  23. typedef enum match_s {
  24.     MATCH_NONE,
  25.     MATCH_SETUP,
  26.     MATCH_PREGAME,
  27.     MATCH_GAME,
  28.     MATCH_POST
  29. } match_t;
  30.  
  31. typedef enum {
  32.     ELECT_NONE,
  33.     ELECT_MATCH,
  34.     ELECT_ADMIN,
  35.     ELECT_MAP
  36. } elect_t;
  37.  
  38. typedef struct ctfgame_s
  39. {
  40.     int team1, team2;
  41.     int total1, total2; // these are only set when going into intermission!
  42.     float last_flag_capture;
  43.     int last_capture_team;
  44.  
  45.     match_t match;        // match state
  46.     float matchtime;    // time for match start/end (depends on state)
  47.     int lasttime;        // last time update
  48.     qboolean countdown;    // has audio countdown started?
  49.  
  50.     elect_t election;    // election type
  51.     edict_t *etarget;    // for admin election, who's being elected
  52.     char elevel[32];    // for map election, target level
  53.     int evotes;            // votes so far
  54.     int needvotes;        // votes needed
  55.     float electtime;    // remaining time until election times out
  56.     char emsg[256];        // election name
  57.     int warnactive; // true if stat string 30 is active
  58.  
  59.  
  60.     ghost_t ghosts[MAX_CLIENTS]; // ghost codes
  61. } ctfgame_t;
  62.  
  63. ctfgame_t ctfgame;
  64.  
  65. cvar_t *ctf;
  66. cvar_t *ctf_forcejoin;
  67.  
  68. cvar_t *competition;
  69. cvar_t *matchlock;
  70. cvar_t *electpercentage;
  71. cvar_t *matchtime;
  72. cvar_t *matchsetuptime;
  73. cvar_t *matchstarttime;
  74. cvar_t *admin_password;
  75. cvar_t *allow_admin;
  76. cvar_t *warp_list;
  77. cvar_t *warn_unbalanced;
  78.  
  79. // Index for various CTF pics, this saves us from calling gi.imageindex
  80. // all the time and saves a few CPU cycles since we don't have to do
  81. // a bunch of string compares all the time.
  82. // These are set in CTFPrecache() called from worldspawn
  83. int imageindex_i_ctf1;
  84. int imageindex_i_ctf2;
  85. int imageindex_i_ctf1d;
  86. int imageindex_i_ctf2d;
  87. int imageindex_i_ctf1t;
  88. int imageindex_i_ctf2t;
  89. int imageindex_i_ctfj;
  90. int imageindex_sbfctf1;
  91. int imageindex_sbfctf2;
  92. int imageindex_ctfsb1;
  93. int imageindex_ctfsb2;
  94.  
  95. char *ctf_statusbar =
  96. "yb    -24 "
  97.  
  98. // health
  99. "xv    0 "
  100. "hnum "
  101. "xv    50 "
  102. "pic 0 "
  103.  
  104. // ammo
  105. "if 2 "
  106. "    xv    100 "
  107. "    anum "
  108. "    xv    150 "
  109. "    pic 2 "
  110. "endif "
  111.  
  112. // armor
  113. "if 4 "
  114. "    xv    200 "
  115. "    rnum "
  116. "    xv    250 "
  117. "    pic 4 "
  118. "endif "
  119.  
  120. // selected item
  121. "if 6 "
  122. "    xv    296 "
  123. "    pic 6 "
  124. "endif "
  125.  
  126. "yb    -50 "
  127.  
  128. // picked up item
  129. "if 7 "
  130. "    xv    0 "
  131. "    pic 7 "
  132. "    xv    26 "
  133. "    yb    -42 "
  134. "    stat_string 8 "
  135. "    yb    -50 "
  136. "endif "
  137.  
  138. // timer
  139. "if 9 "
  140.   "xv 246 "
  141.   "num 2 10 "
  142.   "xv 296 "
  143.   "pic 9 "
  144. "endif "
  145.  
  146. //  help / weapon icon 
  147. "if 11 "
  148.   "xv 148 "
  149.   "pic 11 "
  150. "endif "
  151.  
  152. //  frags
  153. "xr    -50 "
  154. "yt 2 "
  155. "num 3 14 "
  156.  
  157. //tech
  158. "yb -129 "
  159. "if 26 "
  160.   "xr -26 "
  161.   "pic 26 "
  162. "endif "
  163.  
  164. // red team
  165. "yb -102 "
  166. "if 17 "
  167.   "xr -26 "
  168.   "pic 17 "
  169. "endif "
  170. "xr -62 "
  171. "num 2 18 "
  172. //joined overlay
  173. "if 22 "
  174.   "yb -104 "
  175.   "xr -28 "
  176.   "pic 22 "
  177. "endif "
  178.  
  179. // blue team
  180. "yb -75 "
  181. "if 19 "
  182.   "xr -26 "
  183.   "pic 19 "
  184. "endif "
  185. "xr -62 "
  186. "num 2 20 "
  187. "if 23 "
  188.   "yb -77 "
  189.   "xr -28 "
  190.   "pic 23 "
  191. "endif "
  192.  
  193. // have flag graph
  194. "if 21 "
  195.   "yt 26 "
  196.   "xr -24 "
  197.   "pic 21 "
  198. "endif "
  199.  
  200. // id view state
  201. "if 27 "
  202.   "xv 112 "
  203.   "yb -58 "
  204.   "stat_string 27 "
  205. "endif "
  206.  
  207. "if 29 "
  208.   "xv 96 "
  209.   "yb -58 "
  210.   "pic 29 "
  211. "endif "
  212.  
  213. "if 28 "
  214.   "xl 0 "
  215.   "yb -78 "
  216.   "stat_string 28 "
  217. "endif "
  218.  
  219. "if 30 "
  220.   "xl 0 "
  221.   "yb -88 "
  222.   "stat_string 30 "
  223. "endif "
  224. ;
  225.  
  226. static char *tnames[] = {
  227.     "item_tech1", "item_tech2", "item_tech3", "item_tech4",
  228.     NULL
  229. };
  230.  
  231. void stuffcmd(edict_t *ent, char *s)     
  232. {
  233.        gi.WriteByte (11);            
  234.     gi.WriteString (s);
  235.     gi.unicast (ent, true);    
  236. }
  237.  
  238. /*--------------------------------------------------------------------------*/
  239.  
  240. /*
  241. =================
  242. findradius
  243.  
  244. Returns entities that have origins within a spherical area
  245.  
  246. findradius (origin, radius)
  247. =================
  248. */
  249. static edict_t *loc_findradius (edict_t *from, vec3_t org, float rad)
  250. {
  251.     vec3_t    eorg;
  252.     int        j;
  253.  
  254.     if (!from)
  255.         from = g_edicts;
  256.     else
  257.         from++;
  258.     for ( ; from < &g_edicts[globals.num_edicts]; from++)
  259.     {
  260.         if (!from->inuse)
  261.             continue;
  262. #if 0
  263.         if (from->solid == SOLID_NOT)
  264.             continue;
  265. #endif
  266.         for (j=0 ; j<3 ; j++)
  267.             eorg[j] = org[j] - (from->s.origin[j] + (from->mins[j] + from->maxs[j])*0.5);
  268.         if (VectorLength(eorg) > rad)
  269.             continue;
  270.         return from;
  271.     }
  272.  
  273.     return NULL;
  274. }
  275.  
  276. static void loc_buildboxpoints(vec3_t p[8], vec3_t org, vec3_t mins, vec3_t maxs)
  277. {
  278.     VectorAdd(org, mins, p[0]);
  279.     VectorCopy(p[0], p[1]);
  280.     p[1][0] -= mins[0];
  281.     VectorCopy(p[0], p[2]);
  282.     p[2][1] -= mins[1];
  283.     VectorCopy(p[0], p[3]);
  284.     p[3][0] -= mins[0];
  285.     p[3][1] -= mins[1];
  286.     VectorAdd(org, maxs, p[4]);
  287.     VectorCopy(p[4], p[5]);
  288.     p[5][0] -= maxs[0];
  289.     VectorCopy(p[0], p[6]);
  290.     p[6][1] -= maxs[1];
  291.     VectorCopy(p[0], p[7]);
  292.     p[7][0] -= maxs[0];
  293.     p[7][1] -= maxs[1];
  294. }
  295.  
  296. static qboolean loc_CanSee (edict_t *targ, edict_t *inflictor)
  297. {
  298.     trace_t    trace;
  299.     vec3_t    targpoints[8];
  300.     int i;
  301.     vec3_t viewpoint;
  302.  
  303. // bmodels need special checking because their origin is 0,0,0
  304.     if (targ->movetype == MOVETYPE_PUSH)
  305.         return false; // bmodels not supported
  306.  
  307.     loc_buildboxpoints(targpoints, targ->s.origin, targ->mins, targ->maxs);
  308.     
  309.     VectorCopy(inflictor->s.origin, viewpoint);
  310.     viewpoint[2] += inflictor->viewheight;
  311.  
  312.     for (i = 0; i < 8; i++) {
  313.         trace = gi.trace (viewpoint, vec3_origin, vec3_origin, targpoints[i], inflictor, MASK_SOLID);
  314.         if (trace.fraction == 1.0)
  315.             return true;
  316.     }
  317.  
  318.     return false;
  319. }
  320.  
  321. /*--------------------------------------------------------------------------*/
  322.  
  323. static gitem_t *flag1_item;
  324. static gitem_t *flag2_item;
  325.  
  326. void CTFSpawn(void)
  327. {
  328.     if (!flag1_item)
  329.         flag1_item = FindItemByClassname("item_flag_team1");
  330.     if (!flag2_item)
  331.         flag2_item = FindItemByClassname("item_flag_team2");
  332.     memset(&ctfgame, 0, sizeof(ctfgame));
  333.     CTFSetupTechSpawn();
  334.  
  335.     if (competition->value > 1) {
  336.         ctfgame.match = MATCH_SETUP;
  337.         ctfgame.matchtime = level.time + matchsetuptime->value * 60;
  338.     }
  339. }
  340.  
  341. void CTFInit(void)
  342. {
  343.     ctf = gi.cvar("ctf", "1", CVAR_SERVERINFO);
  344.     ctf_forcejoin = gi.cvar("ctf_forcejoin", "", 0);
  345.     competition = gi.cvar("competition", "0", CVAR_SERVERINFO);
  346.     matchlock = gi.cvar("matchlock", "1", CVAR_SERVERINFO);
  347.     electpercentage = gi.cvar("electpercentage", "66", 0);
  348.     matchtime = gi.cvar("matchtime", "20", CVAR_SERVERINFO);
  349.     matchsetuptime = gi.cvar("matchsetuptime", "10", 0);
  350.     matchstarttime = gi.cvar("matchstarttime", "20", 0);
  351.     admin_password = gi.cvar("admin_password", "", 0);
  352.     allow_admin = gi.cvar("allow_admin", "1", 0);
  353.     warp_list = gi.cvar("warp_list", "q2ctf1 q2ctf2 q2ctf3 q2ctf4 q2ctf5", 0);
  354.     warn_unbalanced = gi.cvar("warn_unbalanced", "1", 0);
  355. }
  356.  
  357. /*
  358.  * Precache CTF items
  359.  */
  360.  
  361. void CTFPrecache(void)
  362. {
  363.     imageindex_i_ctf1 =   gi.imageindex("i_ctf1"); 
  364.     imageindex_i_ctf2 =   gi.imageindex("i_ctf2"); 
  365.     imageindex_i_ctf1d =  gi.imageindex("i_ctf1d");
  366.     imageindex_i_ctf2d =  gi.imageindex("i_ctf2d");
  367.     imageindex_i_ctf1t =  gi.imageindex("i_ctf1t");
  368.     imageindex_i_ctf2t =  gi.imageindex("i_ctf2t");
  369.     imageindex_i_ctfj =   gi.imageindex("i_ctfj"); 
  370.     imageindex_sbfctf1 =  gi.imageindex("sbfctf1");
  371.     imageindex_sbfctf2 =  gi.imageindex("sbfctf2");
  372.     imageindex_ctfsb1 =   gi.imageindex("ctfsb1");
  373.     imageindex_ctfsb2 =   gi.imageindex("ctfsb2");
  374. }
  375.  
  376. /*--------------------------------------------------------------------------*/
  377.  
  378. char *CTFTeamName(int team)
  379. {
  380.     switch (team) {
  381.     case CTF_TEAM1:
  382.         return "RED";
  383.     case CTF_TEAM2:
  384.         return "BLUE";
  385.     }
  386.     return "UNKNOWN"; // Hanzo pointed out this was spelled wrong as "UKNOWN"
  387. }
  388.  
  389. char *CTFOtherTeamName(int team)
  390. {
  391.     switch (team) {
  392.     case CTF_TEAM1:
  393.         return "BLUE";
  394.     case CTF_TEAM2:
  395.         return "RED";
  396.     }
  397.     return "UNKNOWN"; // Hanzo pointed out this was spelled wrong as "UKNOWN"
  398. }
  399.  
  400. int CTFOtherTeam(int team)
  401. {
  402.     switch (team) {
  403.     case CTF_TEAM1:
  404.         return CTF_TEAM2;
  405.     case CTF_TEAM2:
  406.         return CTF_TEAM1;
  407.     }
  408.     return -1; // invalid value
  409. }
  410.  
  411. /*--------------------------------------------------------------------------*/
  412.  
  413. edict_t *SelectRandomDeathmatchSpawnPoint (void);
  414. edict_t *SelectFarthestDeathmatchSpawnPoint (void);
  415. float    PlayersRangeFromSpot (edict_t *spot);
  416.  
  417. void CTFAssignSkin(edict_t *ent, char *s)
  418. {
  419.     int playernum = ent-g_edicts-1;
  420.     char *p;
  421.     char t[64];
  422.  
  423.     Com_sprintf(t, sizeof(t), "%s", s);
  424.  
  425.     if ((p = strchr(t, '/')) != NULL)
  426.         p[1] = 0;
  427.     else
  428.         strcpy(t, "male/");
  429.  
  430.     switch (ent->client->resp.ctf_team) {
  431.     case CTF_TEAM1:
  432.         gi.configstring (CS_PLAYERSKINS+playernum, va("%s\\%s%s", 
  433.             ent->client->pers.netname, t, CTF_TEAM1_SKIN) );
  434.         break;
  435.     case CTF_TEAM2:
  436.         gi.configstring (CS_PLAYERSKINS+playernum,
  437.             va("%s\\%s%s", ent->client->pers.netname, t, CTF_TEAM2_SKIN) );
  438.         break;
  439.     default:
  440.         gi.configstring (CS_PLAYERSKINS+playernum, 
  441.             va("%s\\%s", ent->client->pers.netname, s) );
  442.         break;
  443.     }
  444. //    gi.cprintf(ent, PRINT_HIGH, "You have been assigned to %s team.\n", ent->client->pers.netname);
  445. }
  446.  
  447. void CTFAssignTeam(gclient_t *who)
  448. {
  449.     edict_t        *player;
  450.     int i;
  451.     int team1count = 0, team2count = 0;
  452.  
  453.     who->resp.ctf_state = 0;
  454.  
  455.     if (!((int)dmflags->value & DF_CTF_FORCEJOIN)) {
  456.         who->resp.ctf_team = CTF_NOTEAM;
  457.         return;
  458.     }
  459.  
  460.     for (i = 1; i <= maxclients->value; i++) {
  461.         player = &g_edicts[i];
  462.  
  463.         if (!player->inuse || player->client == who)
  464.             continue;
  465.  
  466.         switch (player->client->resp.ctf_team) {
  467.         case CTF_TEAM1:
  468.             team1count++;
  469.             break;
  470.         case CTF_TEAM2:
  471.             team2count++;
  472.         }
  473.     }
  474.     if (team1count < team2count)
  475.         who->resp.ctf_team = CTF_TEAM1;
  476.     else if (team2count < team1count)
  477.         who->resp.ctf_team = CTF_TEAM2;
  478.     else if (rand() & 1)
  479.         who->resp.ctf_team = CTF_TEAM1;
  480.     else
  481.         who->resp.ctf_team = CTF_TEAM2;
  482. }
  483.  
  484. /*
  485. ================
  486. SelectCTFSpawnPoint
  487.  
  488. go to a ctf point, but NOT the two points closest
  489. to other players
  490. ================
  491. */
  492. edict_t *SelectCTFSpawnPoint (edict_t *ent)
  493. {
  494.     edict_t    *spot, *spot1, *spot2;
  495.     int        count = 0;
  496.     int        selection;
  497.     float    range, range1, range2;
  498.     char    *cname;
  499.  
  500.     if (ent->client->resp.ctf_state)
  501.         if ( (int)(dmflags->value) & DF_SPAWN_FARTHEST)
  502.             return SelectFarthestDeathmatchSpawnPoint ();
  503.         else
  504.             return SelectRandomDeathmatchSpawnPoint ();
  505.  
  506.     ent->client->resp.ctf_state++;
  507.  
  508.     switch (ent->client->resp.ctf_team) {
  509.     case CTF_TEAM1:
  510.         cname = "info_player_team1";
  511.         break;
  512.     case CTF_TEAM2:
  513.         cname = "info_player_team2";
  514.         break;
  515.     default:
  516.         return SelectRandomDeathmatchSpawnPoint();
  517.     }
  518.  
  519.     spot = NULL;
  520.     range1 = range2 = 99999;
  521.     spot1 = spot2 = NULL;
  522.  
  523.     while ((spot = G_Find (spot, FOFS(classname), cname)) != NULL)
  524.     {
  525.         count++;
  526.         range = PlayersRangeFromSpot(spot);
  527.         if (range < range1)
  528.         {
  529.             range1 = range;
  530.             spot1 = spot;
  531.         }
  532.         else if (range < range2)
  533.         {
  534.             range2 = range;
  535.             spot2 = spot;
  536.         }
  537.     }
  538.  
  539.     if (!count)
  540.         return SelectRandomDeathmatchSpawnPoint();
  541.  
  542.     if (count <= 2)
  543.     {
  544.         spot1 = spot2 = NULL;
  545.     }
  546.     else
  547.         count -= 2;
  548.  
  549.     selection = rand() % count;
  550.  
  551.     spot = NULL;
  552.     do
  553.     {
  554.         spot = G_Find (spot, FOFS(classname), cname);
  555.         if (spot == spot1 || spot == spot2)
  556.             selection++;
  557.     } while(selection--);
  558.  
  559.     return spot;
  560. }
  561.  
  562. /*------------------------------------------------------------------------*/
  563. /*
  564. CTFFragBonuses
  565.  
  566. Calculate the bonuses for flag defense, flag carrier defense, etc.
  567. Note that bonuses are not cumaltive.  You get one, they are in importance
  568. order.
  569. */
  570. void CTFFragBonuses(edict_t *targ, edict_t *inflictor, edict_t *attacker)
  571. {
  572.     int i;
  573.     edict_t *ent;
  574.     gitem_t *flag_item, *enemy_flag_item;
  575.     int otherteam;
  576.     edict_t *flag, *carrier;
  577.     char *c;
  578.     vec3_t v1, v2;
  579.  
  580.     if (targ->client && attacker->client) {
  581.         if (attacker->client->resp.ghost)
  582.             if (attacker != targ)
  583.                 attacker->client->resp.ghost->kills++;
  584.         if (targ->client->resp.ghost)
  585.             targ->client->resp.ghost->deaths++;
  586.     }
  587.  
  588.     // no bonus for fragging yourself
  589.     if (!targ->client || !attacker->client || targ == attacker)
  590.         return;
  591.  
  592.     otherteam = CTFOtherTeam(targ->client->resp.ctf_team);
  593.     if (otherteam < 0)
  594.         return; // whoever died isn't on a team
  595.  
  596.     // same team, if the flag at base, check to he has the enemy flag
  597.     if (targ->client->resp.ctf_team == CTF_TEAM1) {
  598.         flag_item = flag1_item;
  599.         enemy_flag_item = flag2_item;
  600.     } else {
  601.         flag_item = flag2_item;
  602.         enemy_flag_item = flag1_item;
  603.     }
  604.  
  605.     // did the attacker frag the flag carrier?
  606.     if (targ->client->pers.inventory[ITEM_INDEX(enemy_flag_item)]) {
  607.         attacker->client->resp.ctf_lastfraggedcarrier = level.time;
  608.         attacker->client->resp.score += CTF_FRAG_CARRIER_BONUS;
  609.         gi.cprintf(attacker, PRINT_MEDIUM, "BONUS: %d points for fragging enemy flag carrier.\n",
  610.             CTF_FRAG_CARRIER_BONUS);
  611.  
  612.         // the target had the flag, clear the hurt carrier
  613.         // field on the other team
  614.         for (i = 1; i <= maxclients->value; i++) {
  615.             ent = g_edicts + i;
  616.             if (ent->inuse && ent->client->resp.ctf_team == otherteam)
  617.                 ent->client->resp.ctf_lasthurtcarrier = 0;
  618.         }
  619.         return;
  620.     }
  621.  
  622.     if (targ->client->resp.ctf_lasthurtcarrier &&
  623.         level.time - targ->client->resp.ctf_lasthurtcarrier < CTF_CARRIER_DANGER_PROTECT_TIMEOUT &&
  624.         !attacker->client->pers.inventory[ITEM_INDEX(flag_item)]) {
  625.         // attacker is on the same team as the flag carrier and
  626.         // fragged a guy who hurt our flag carrier
  627.         attacker->client->resp.score += CTF_CARRIER_DANGER_PROTECT_BONUS;
  628.         gi.bprintf(PRINT_MEDIUM, "%s defends %s's flag carrier against an agressive enemy\n",
  629.             attacker->client->pers.netname, 
  630.             CTFTeamName(attacker->client->resp.ctf_team));
  631.         if (attacker->client->resp.ghost)
  632.             attacker->client->resp.ghost->carrierdef++;
  633.         return;
  634.     }
  635.  
  636.     // flag and flag carrier area defense bonuses
  637.  
  638.     // we have to find the flag and carrier entities
  639.  
  640.     // find the flag
  641.     switch (attacker->client->resp.ctf_team) {
  642.     case CTF_TEAM1:
  643.         c = "item_flag_team1";
  644.         break;
  645.     case CTF_TEAM2:
  646.         c = "item_flag_team2";
  647.         break;
  648.     default:
  649.         return;
  650.     }
  651.  
  652.     flag = NULL;
  653.     while ((flag = G_Find (flag, FOFS(classname), c)) != NULL) {
  654.         if (!(flag->spawnflags & DROPPED_ITEM))
  655.             break;
  656.     }
  657.  
  658.     if (!flag)
  659.         return; // can't find attacker's flag
  660.  
  661.     // find attacker's team's flag carrier
  662.     for (i = 1; i <= maxclients->value; i++) {
  663.         carrier = g_edicts + i;
  664.         if (carrier->inuse && 
  665.             carrier->client->pers.inventory[ITEM_INDEX(flag_item)])
  666.             break;
  667.         carrier = NULL;
  668.     }
  669.  
  670.     // ok we have the attackers flag and a pointer to the carrier
  671.  
  672.     // check to see if we are defending the base's flag
  673.     VectorSubtract(targ->s.origin, flag->s.origin, v1);
  674.     VectorSubtract(attacker->s.origin, flag->s.origin, v2);
  675.  
  676.     if ((VectorLength(v1) < CTF_TARGET_PROTECT_RADIUS ||
  677.         VectorLength(v2) < CTF_TARGET_PROTECT_RADIUS ||
  678.         loc_CanSee(flag, targ) || loc_CanSee(flag, attacker)) &&
  679.         attacker->client->resp.ctf_team != targ->client->resp.ctf_team) {
  680.         // we defended the base flag
  681.         attacker->client->resp.score += CTF_FLAG_DEFENSE_BONUS;
  682.         if (flag->solid == SOLID_NOT)
  683.             gi.bprintf(PRINT_MEDIUM, "%s defends the %s base.\n",
  684.                 attacker->client->pers.netname, 
  685.                 CTFTeamName(attacker->client->resp.ctf_team));
  686.         else
  687.             gi.bprintf(PRINT_MEDIUM, "%s defends the %s flag.\n",
  688.                 attacker->client->pers.netname, 
  689.                 CTFTeamName(attacker->client->resp.ctf_team));
  690.         if (attacker->client->resp.ghost)
  691.             attacker->client->resp.ghost->basedef++;
  692.         return;
  693.     }
  694.  
  695.     if (carrier && carrier != attacker) {
  696.         VectorSubtract(targ->s.origin, carrier->s.origin, v1);
  697.         VectorSubtract(attacker->s.origin, carrier->s.origin, v1);
  698.  
  699.         if (VectorLength(v1) < CTF_ATTACKER_PROTECT_RADIUS ||
  700.             VectorLength(v2) < CTF_ATTACKER_PROTECT_RADIUS ||
  701.             loc_CanSee(carrier, targ) || loc_CanSee(carrier, attacker)) {
  702.             attacker->client->resp.score += CTF_CARRIER_PROTECT_BONUS;
  703.             gi.bprintf(PRINT_MEDIUM, "%s defends the %s's flag carrier.\n",
  704.                 attacker->client->pers.netname, 
  705.                 CTFTeamName(attacker->client->resp.ctf_team));
  706.             if (attacker->client->resp.ghost)
  707.                 attacker->client->resp.ghost->carrierdef++;
  708.             return;
  709.         }
  710.     }
  711. }
  712.  
  713. void CTFCheckHurtCarrier(edict_t *targ, edict_t *attacker)
  714. {
  715.     gitem_t *flag_item;
  716.  
  717.     if (!targ->client || !attacker->client)
  718.         return;
  719.  
  720.     if (targ->client->resp.ctf_team == CTF_TEAM1)
  721.         flag_item = flag2_item;
  722.     else
  723.         flag_item = flag1_item;
  724.  
  725.     if (targ->client->pers.inventory[ITEM_INDEX(flag_item)] &&
  726.         targ->client->resp.ctf_team != attacker->client->resp.ctf_team)
  727.         attacker->client->resp.ctf_lasthurtcarrier = level.time;
  728. }
  729.  
  730.  
  731. /*------------------------------------------------------------------------*/
  732.  
  733. void CTFResetFlag(int ctf_team)
  734. {
  735.     char *c;
  736.     edict_t *ent;
  737.  
  738.     switch (ctf_team) {
  739.     case CTF_TEAM1:
  740.         c = "item_flag_team1";
  741.         break;
  742.     case CTF_TEAM2:
  743.         c = "item_flag_team2";
  744.         break;
  745.     default:
  746.         return;
  747.     }
  748.  
  749.     ent = NULL;
  750.     while ((ent = G_Find (ent, FOFS(classname), c)) != NULL) {
  751.         if (ent->spawnflags & DROPPED_ITEM)
  752.             G_FreeEdict(ent);
  753.         else {
  754.             ent->svflags &= ~SVF_NOCLIENT;
  755.             ent->solid = SOLID_TRIGGER;
  756.             gi.linkentity(ent);
  757.             ent->s.event = EV_ITEM_RESPAWN;
  758.         }
  759.     }
  760. }
  761.  
  762. void CTFResetFlags(void)
  763. {
  764.     CTFResetFlag(CTF_TEAM1);
  765.     CTFResetFlag(CTF_TEAM2);
  766. }
  767.  
  768. qboolean CTFPickup_Flag(edict_t *ent, edict_t *other)
  769. {
  770.     int ctf_team;
  771.     int i;
  772.     edict_t *player;
  773.     gitem_t *flag_item, *enemy_flag_item;
  774.  
  775.     // figure out what team this flag is
  776.     if (strcmp(ent->classname, "item_flag_team1") == 0)
  777.         ctf_team = CTF_TEAM1;
  778.     else if (strcmp(ent->classname, "item_flag_team2") == 0)
  779.         ctf_team = CTF_TEAM2;
  780.     else {
  781.         gi.cprintf(ent, PRINT_HIGH, "Don't know what team the flag is on.\n");
  782.         return false;
  783.     }
  784.  
  785.     // same team, if the flag at base, check to he has the enemy flag
  786.     if (ctf_team == CTF_TEAM1) {
  787.         flag_item = flag1_item;
  788.         enemy_flag_item = flag2_item;
  789.     } else {
  790.         flag_item = flag2_item;
  791.         enemy_flag_item = flag1_item;
  792.     }
  793.  
  794.     if (ctf_team == other->client->resp.ctf_team) {
  795.  
  796.         if (!(ent->spawnflags & DROPPED_ITEM)) {
  797.             // the flag is at home base.  if the player has the enemy
  798.             // flag, he's just won!
  799.         
  800.             if (other->client->pers.inventory[ITEM_INDEX(enemy_flag_item)]) {
  801.                 gi.bprintf(PRINT_HIGH, "%s captured the %s flag!\n",
  802.                         other->client->pers.netname, CTFOtherTeamName(ctf_team));
  803.                 other->client->pers.inventory[ITEM_INDEX(enemy_flag_item)] = 0;
  804.  
  805.                 ctfgame.last_flag_capture = level.time;
  806.                 ctfgame.last_capture_team = ctf_team;
  807.                 if (ctf_team == CTF_TEAM1)
  808.                     ctfgame.team1++;
  809.                 else
  810.                     ctfgame.team2++;
  811.  
  812.                 gi.sound (ent, CHAN_RELIABLE+CHAN_NO_PHS_ADD+CHAN_VOICE, gi.soundindex("ctf/flagcap.wav"), 1, ATTN_NONE, 0);
  813.  
  814.                 // other gets another 10 frag bonus
  815.                 other->client->resp.score += CTF_CAPTURE_BONUS;
  816.                 if (other->client->resp.ghost)
  817.                     other->client->resp.ghost->caps++;
  818.  
  819.                 // Ok, let's do the player loop, hand out the bonuses
  820.                 for (i = 1; i <= maxclients->value; i++) {
  821.                     player = &g_edicts[i];
  822.                     if (!player->inuse)
  823.                         continue;
  824.  
  825.                     if (player->client->resp.ctf_team != other->client->resp.ctf_team)
  826.                         player->client->resp.ctf_lasthurtcarrier = -5;
  827.                     else if (player->client->resp.ctf_team == other->client->resp.ctf_team) {
  828.                         if (player != other)
  829.                             player->client->resp.score += CTF_TEAM_BONUS;
  830.                         // award extra points for capture assists
  831.                         if (player->client->resp.ctf_lastreturnedflag + CTF_RETURN_FLAG_ASSIST_TIMEOUT > level.time) {
  832.                             gi.bprintf(PRINT_HIGH, "%s gets an assist for returning the flag!\n", player->client->pers.netname);
  833.                             player->client->resp.score += CTF_RETURN_FLAG_ASSIST_BONUS;
  834.                         }
  835.                         if (player->client->resp.ctf_lastfraggedcarrier + CTF_FRAG_CARRIER_ASSIST_TIMEOUT > level.time) {
  836.                             gi.bprintf(PRINT_HIGH, "%s gets an assist for fragging the flag carrier!\n", player->client->pers.netname);
  837.                             player->client->resp.score += CTF_FRAG_CARRIER_ASSIST_BONUS;
  838.                         }
  839.                     }
  840.                 }
  841.  
  842.                 CTFResetFlags();
  843.                 return false;
  844.             }
  845.             return false; // its at home base already
  846.         }    
  847.         // hey, its not home.  return it by teleporting it back
  848.         gi.bprintf(PRINT_HIGH, "%s returned the %s flag!\n", 
  849.             other->client->pers.netname, CTFTeamName(ctf_team));
  850.         other->client->resp.score += CTF_RECOVERY_BONUS;
  851.         other->client->resp.ctf_lastreturnedflag = level.time;
  852.         gi.sound (ent, CHAN_RELIABLE+CHAN_NO_PHS_ADD+CHAN_VOICE, gi.soundindex("ctf/flagret.wav"), 1, ATTN_NONE, 0);
  853.         //CTFResetFlag will remove this entity!  We must return false
  854.         CTFResetFlag(ctf_team);
  855.         return false;
  856.     }
  857.  
  858.     // hey, its not our flag, pick it up
  859.     gi.bprintf(PRINT_HIGH, "%s got the %s flag!\n",
  860.         other->client->pers.netname, CTFTeamName(ctf_team));
  861.     other->client->resp.score += CTF_FLAG_BONUS;
  862.  
  863.     other->client->pers.inventory[ITEM_INDEX(flag_item)] = 1;
  864.     other->client->resp.ctf_flagsince = level.time;
  865.  
  866.     // pick up the flag
  867.     // if it's not a dropped flag, we just make is disappear
  868.     // if it's dropped, it will be removed by the pickup caller
  869.     if (!(ent->spawnflags & DROPPED_ITEM)) {
  870.         ent->flags |= FL_RESPAWN;
  871.         ent->svflags |= SVF_NOCLIENT;
  872.         ent->solid = SOLID_NOT;
  873.     }
  874.     return true;
  875. }
  876.  
  877. static void CTFDropFlagTouch(edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf)
  878. {
  879.     //owner (who dropped us) can't touch for two secs
  880.     if (other == ent->owner && 
  881.         ent->nextthink - level.time > CTF_AUTO_FLAG_RETURN_TIMEOUT-2)
  882.         return;
  883.  
  884.     Touch_Item (ent, other, plane, surf);
  885. }
  886.  
  887. static void CTFDropFlagThink(edict_t *ent)
  888. {
  889.     // auto return the flag
  890.     // reset flag will remove ourselves
  891.     if (strcmp(ent->classname, "item_flag_team1") == 0) {
  892.         CTFResetFlag(CTF_TEAM1);
  893.         gi.bprintf(PRINT_HIGH, "The %s flag has returned!\n",
  894.             CTFTeamName(CTF_TEAM1));
  895.     } else if (strcmp(ent->classname, "item_flag_team2") == 0) {
  896.         CTFResetFlag(CTF_TEAM2);
  897.         gi.bprintf(PRINT_HIGH, "The %s flag has returned!\n",
  898.             CTFTeamName(CTF_TEAM2));
  899.     }
  900. }
  901.  
  902. // Called from PlayerDie, to drop the flag from a dying player
  903. void CTFDeadDropFlag(edict_t *self)
  904. {
  905.     edict_t *dropped = NULL;
  906.  
  907.     if (self->client->pers.inventory[ITEM_INDEX(flag1_item)]) {
  908.         dropped = Drop_Item(self, flag1_item);
  909.         self->client->pers.inventory[ITEM_INDEX(flag1_item)] = 0;
  910.         gi.bprintf(PRINT_HIGH, "%s lost the %s flag!\n",
  911.             self->client->pers.netname, CTFTeamName(CTF_TEAM1));
  912.     } else if (self->client->pers.inventory[ITEM_INDEX(flag2_item)]) {
  913.         dropped = Drop_Item(self, flag2_item);
  914.         self->client->pers.inventory[ITEM_INDEX(flag2_item)] = 0;
  915.         gi.bprintf(PRINT_HIGH, "%s lost the %s flag!\n",
  916.             self->client->pers.netname, CTFTeamName(CTF_TEAM2));
  917.     }
  918.  
  919.     if (dropped) {
  920.         dropped->think = CTFDropFlagThink;
  921.         dropped->nextthink = level.time + CTF_AUTO_FLAG_RETURN_TIMEOUT;
  922.         dropped->touch = CTFDropFlagTouch;
  923.     }
  924. }
  925.  
  926. qboolean CTFDrop_Flag(edict_t *ent, gitem_t *item)
  927. {
  928.     if (rand() & 1) 
  929.         gi.cprintf(ent, PRINT_HIGH, "Only lusers drop flags.\n");
  930.     else
  931.         gi.cprintf(ent, PRINT_HIGH, "Winners don't drop flags.\n");
  932.     return false;
  933. }
  934.  
  935. static void CTFFlagThink(edict_t *ent)
  936. {
  937.     if (ent->solid != SOLID_NOT)
  938.         ent->s.frame = 173 + (((ent->s.frame - 173) + 1) % 16);
  939.     ent->nextthink = level.time + FRAMETIME;
  940. }
  941.  
  942.  
  943. void CTFFlagSetup (edict_t *ent)
  944. {
  945.     trace_t        tr;
  946.     vec3_t        dest;
  947.     float        *v;
  948.  
  949.     v = tv(-15,-15,-15);
  950.     VectorCopy (v, ent->mins);
  951.     v = tv(15,15,15);
  952.     VectorCopy (v, ent->maxs);
  953.  
  954.     if (ent->model)
  955.         gi.setmodel (ent, ent->model);
  956.     else
  957.         gi.setmodel (ent, ent->item->world_model);
  958.     ent->solid = SOLID_TRIGGER;
  959.     ent->movetype = MOVETYPE_TOSS;  
  960.     ent->touch = Touch_Item;
  961.  
  962.     v = tv(0,0,-128);
  963.     VectorAdd (ent->s.origin, v, dest);
  964.  
  965.     tr = gi.trace (ent->s.origin, ent->mins, ent->maxs, dest, ent, MASK_SOLID);
  966.     if (tr.startsolid)
  967.     {
  968.         gi.dprintf ("CTFFlagSetup: %s startsolid at %s\n", ent->classname, vtos(ent->s.origin));
  969.         G_FreeEdict (ent);
  970.         return;
  971.     }
  972.  
  973.     VectorCopy (tr.endpos, ent->s.origin);
  974.  
  975.     gi.linkentity (ent);
  976.  
  977.     ent->nextthink = level.time + FRAMETIME;
  978.     ent->think = CTFFlagThink;
  979. }
  980.  
  981. void CTFEffects(edict_t *player)
  982. {
  983.     player->s.effects &= ~(EF_FLAG1 | EF_FLAG2);
  984.     if (player->health > 0) {
  985.         if (player->client->pers.inventory[ITEM_INDEX(flag1_item)]) {
  986.             player->s.effects |= EF_FLAG1;
  987.         }
  988.         if (player->client->pers.inventory[ITEM_INDEX(flag2_item)]) {
  989.             player->s.effects |= EF_FLAG2;
  990.         }
  991.     }
  992.  
  993.     if (player->client->pers.inventory[ITEM_INDEX(flag1_item)])
  994.         player->s.modelindex3 = gi.modelindex("players/male/flag1.md2");
  995.     else if (player->client->pers.inventory[ITEM_INDEX(flag2_item)])
  996.         player->s.modelindex3 = gi.modelindex("players/male/flag2.md2");
  997.     else
  998.         player->s.modelindex3 = 0;
  999. }
  1000.  
  1001. // called when we enter the intermission
  1002. void CTFCalcScores(void)
  1003. {
  1004.     int i;
  1005.  
  1006.     ctfgame.total1 = ctfgame.total2 = 0;
  1007.     for (i = 0; i < maxclients->value; i++) {
  1008.         if (!g_edicts[i+1].inuse)
  1009.             continue;
  1010.         if (game.clients[i].resp.ctf_team == CTF_TEAM1)
  1011.             ctfgame.total1 += game.clients[i].resp.score;
  1012.         else if (game.clients[i].resp.ctf_team == CTF_TEAM2)
  1013.             ctfgame.total2 += game.clients[i].resp.score;
  1014.     }
  1015. }
  1016.  
  1017. void CTFID_f (edict_t *ent)
  1018. {
  1019.     if (ent->client->resp.id_state) {
  1020.         gi.cprintf(ent, PRINT_HIGH, "Disabling player identication display.\n");
  1021.         ent->client->resp.id_state = false;
  1022.     } else {
  1023.         gi.cprintf(ent, PRINT_HIGH, "Activating player identication display.\n");
  1024.         ent->client->resp.id_state = true;
  1025.     }
  1026. }
  1027.  
  1028. static void CTFSetIDView(edict_t *ent)
  1029. {
  1030.     vec3_t    forward, dir;
  1031.     trace_t    tr;
  1032.     edict_t    *who, *best;
  1033.     float    bd = 0, d;
  1034.     int i;
  1035.  
  1036.     // only check every few frames
  1037.     if (level.time - ent->client->resp.lastidtime < 0.25)
  1038.         return;
  1039.     ent->client->resp.lastidtime = level.time;
  1040.  
  1041.     ent->client->ps.stats[STAT_CTF_ID_VIEW] = 0;
  1042.     ent->client->ps.stats[STAT_CTF_ID_VIEW_COLOR] = 0;
  1043.  
  1044.     AngleVectors(ent->client->v_angle, forward, NULL, NULL);
  1045.     VectorScale(forward, 1024, forward);
  1046.     VectorAdd(ent->s.origin, forward, forward);
  1047.     tr = gi.trace(ent->s.origin, NULL, NULL, forward, ent, MASK_SOLID);
  1048.     if (tr.fraction < 1 && tr.ent && tr.ent->client) {
  1049.         ent->client->ps.stats[STAT_CTF_ID_VIEW] = 
  1050.             CS_GENERAL + (tr.ent - g_edicts - 1);
  1051.         if (tr.ent->client->resp.ctf_team == CTF_TEAM1)
  1052.             ent->client->ps.stats[STAT_CTF_ID_VIEW_COLOR] = imageindex_sbfctf1;
  1053.         else if (tr.ent->client->resp.ctf_team == CTF_TEAM2)
  1054.             ent->client->ps.stats[STAT_CTF_ID_VIEW_COLOR] = imageindex_sbfctf2;
  1055.         return;
  1056.     }
  1057.  
  1058.     AngleVectors(ent->client->v_angle, forward, NULL, NULL);
  1059.     best = NULL;
  1060.     for (i = 1; i <= maxclients->value; i++) {
  1061.         who = g_edicts + i;
  1062.         if (!who->inuse || who->solid == SOLID_NOT)
  1063.             continue;
  1064.         VectorSubtract(who->s.origin, ent->s.origin, dir);
  1065.         VectorNormalize(dir);
  1066.         d = DotProduct(forward, dir);
  1067.         if (d > bd && loc_CanSee(ent, who)) {
  1068.             bd = d;
  1069.             best = who;
  1070.         }
  1071.     }
  1072.     if (bd > 0.90) {
  1073.         ent->client->ps.stats[STAT_CTF_ID_VIEW] = 
  1074.             CS_GENERAL + (best - g_edicts - 1);
  1075.         if (best->client->resp.ctf_team == CTF_TEAM1)
  1076.             ent->client->ps.stats[STAT_CTF_ID_VIEW_COLOR] = imageindex_sbfctf1;
  1077.         else if (best->client->resp.ctf_team == CTF_TEAM2)
  1078.             ent->client->ps.stats[STAT_CTF_ID_VIEW_COLOR] = imageindex_sbfctf2;
  1079.     }
  1080. }
  1081.  
  1082. void SetCTFStats(edict_t *ent)
  1083. {
  1084.     gitem_t *tech;
  1085.     int i;
  1086.     int p1, p2;
  1087.     edict_t *e;
  1088.  
  1089.     if (ctfgame.match > MATCH_NONE)
  1090.         ent->client->ps.stats[STAT_CTF_MATCH] = CONFIG_CTF_MATCH;
  1091.     else
  1092.         ent->client->ps.stats[STAT_CTF_MATCH] = 0;
  1093.  
  1094.     if (ctfgame.warnactive)
  1095.         ent->client->ps.stats[STAT_CTF_TEAMINFO] = CONFIG_CTF_TEAMINFO;
  1096.     else
  1097.         ent->client->ps.stats[STAT_CTF_TEAMINFO] = 0;
  1098.  
  1099.     //ghosting
  1100.     if (ent->client->resp.ghost) {
  1101.         ent->client->resp.ghost->score = ent->client->resp.score;
  1102.         strcpy(ent->client->resp.ghost->netname, ent->client->pers.netname);
  1103.         ent->client->resp.ghost->number = ent->s.number;
  1104.     }
  1105.  
  1106.     // logo headers for the frag display
  1107.     ent->client->ps.stats[STAT_CTF_TEAM1_HEADER] = imageindex_ctfsb1;
  1108.     ent->client->ps.stats[STAT_CTF_TEAM2_HEADER] = imageindex_ctfsb2;
  1109.  
  1110.     // if during intermission, we must blink the team header of the winning team
  1111.     if (level.intermissiontime && (level.framenum & 8)) { // blink 1/8th second
  1112.         // note that ctfgame.total[12] is set when we go to intermission
  1113.         if (ctfgame.team1 > ctfgame.team2)
  1114.             ent->client->ps.stats[STAT_CTF_TEAM1_HEADER] = 0;
  1115.         else if (ctfgame.team2 > ctfgame.team1)
  1116.             ent->client->ps.stats[STAT_CTF_TEAM2_HEADER] = 0;
  1117.         else if (ctfgame.total1 > ctfgame.total2) // frag tie breaker
  1118.             ent->client->ps.stats[STAT_CTF_TEAM1_HEADER] = 0;
  1119.         else if (ctfgame.total2 > ctfgame.total1) 
  1120.             ent->client->ps.stats[STAT_CTF_TEAM2_HEADER] = 0;
  1121.         else { // tie game!
  1122.             ent->client->ps.stats[STAT_CTF_TEAM1_HEADER] = 0;
  1123.             ent->client->ps.stats[STAT_CTF_TEAM2_HEADER] = 0;
  1124.         }
  1125.     }
  1126.  
  1127.     // tech icon
  1128.     i = 0;
  1129.     ent->client->ps.stats[STAT_CTF_TECH] = 0;
  1130.     while (tnames[i]) {
  1131.         if ((tech = FindItemByClassname(tnames[i])) != NULL &&
  1132.             ent->client->pers.inventory[ITEM_INDEX(tech)]) {
  1133.             ent->client->ps.stats[STAT_CTF_TECH] = gi.imageindex(tech->icon);
  1134.             break;
  1135.         }
  1136.         i++;
  1137.     }
  1138.  
  1139.     // figure out what icon to display for team logos
  1140.     // three states:
  1141.     //   flag at base
  1142.     //   flag taken
  1143.     //   flag dropped
  1144.     p1 = imageindex_i_ctf1;
  1145.     e = G_Find(NULL, FOFS(classname), "item_flag_team1");
  1146.     if (e != NULL) {
  1147.         if (e->solid == SOLID_NOT) {
  1148.             int i;
  1149.  
  1150.             // not at base
  1151.             // check if on player
  1152.             p1 = imageindex_i_ctf1d; // default to dropped
  1153.             for (i = 1; i <= maxclients->value; i++)
  1154.                 if (g_edicts[i].inuse &&
  1155.                     g_edicts[i].client->pers.inventory[ITEM_INDEX(flag1_item)]) {
  1156.                     // enemy has it
  1157.                     p1 = imageindex_i_ctf1t;
  1158.                     break;
  1159.                 }
  1160.         } else if (e->spawnflags & DROPPED_ITEM)
  1161.             p1 = imageindex_i_ctf1d; // must be dropped
  1162.     }
  1163.     p2 = imageindex_i_ctf2;
  1164.     e = G_Find(NULL, FOFS(classname), "item_flag_team2");
  1165.     if (e != NULL) {
  1166.         if (e->solid == SOLID_NOT) {
  1167.             int i;
  1168.  
  1169.             // not at base
  1170.             // check if on player
  1171.             p2 = imageindex_i_ctf2d; // default to dropped
  1172.             for (i = 1; i <= maxclients->value; i++)
  1173.                 if (g_edicts[i].inuse &&
  1174.                     g_edicts[i].client->pers.inventory[ITEM_INDEX(flag2_item)]) {
  1175.                     // enemy has it
  1176.                     p2 = imageindex_i_ctf2t;
  1177.                     break;
  1178.                 }
  1179.         } else if (e->spawnflags & DROPPED_ITEM)
  1180.             p2 = imageindex_i_ctf2d; // must be dropped
  1181.     }
  1182.  
  1183.  
  1184.     ent->client->ps.stats[STAT_CTF_TEAM1_PIC] = p1;
  1185.     ent->client->ps.stats[STAT_CTF_TEAM2_PIC] = p2;
  1186.  
  1187.     if (ctfgame.last_flag_capture && level.time - ctfgame.last_flag_capture < 5) {
  1188.         if (ctfgame.last_capture_team == CTF_TEAM1)
  1189.             if (level.framenum & 8)
  1190.                 ent->client->ps.stats[STAT_CTF_TEAM1_PIC] = p1;
  1191.             else
  1192.                 ent->client->ps.stats[STAT_CTF_TEAM1_PIC] = 0;
  1193.         else
  1194.             if (level.framenum & 8)
  1195.                 ent->client->ps.stats[STAT_CTF_TEAM2_PIC] = p2;
  1196.             else
  1197.                 ent->client->ps.stats[STAT_CTF_TEAM2_PIC] = 0;
  1198.     }
  1199.  
  1200.     ent->client->ps.stats[STAT_CTF_TEAM1_CAPS] = ctfgame.team1;
  1201.     ent->client->ps.stats[STAT_CTF_TEAM2_CAPS] = ctfgame.team2;
  1202.  
  1203.     ent->client->ps.stats[STAT_CTF_FLAG_PIC] = 0;
  1204.     if (ent->client->resp.ctf_team == CTF_TEAM1 &&
  1205.         ent->client->pers.inventory[ITEM_INDEX(flag2_item)] &&
  1206.         (level.framenum & 8))
  1207.         ent->client->ps.stats[STAT_CTF_FLAG_PIC] = imageindex_i_ctf2;
  1208.  
  1209.     else if (ent->client->resp.ctf_team == CTF_TEAM2 &&
  1210.         ent->client->pers.inventory[ITEM_INDEX(flag1_item)] &&
  1211.         (level.framenum & 8))
  1212.         ent->client->ps.stats[STAT_CTF_FLAG_PIC] = imageindex_i_ctf1;
  1213.  
  1214.     ent->client->ps.stats[STAT_CTF_JOINED_TEAM1_PIC] = 0;
  1215.     ent->client->ps.stats[STAT_CTF_JOINED_TEAM2_PIC] = 0;
  1216.     if (ent->client->resp.ctf_team == CTF_TEAM1)
  1217.         ent->client->ps.stats[STAT_CTF_JOINED_TEAM1_PIC] = imageindex_i_ctfj;
  1218.     else if (ent->client->resp.ctf_team == CTF_TEAM2)
  1219.         ent->client->ps.stats[STAT_CTF_JOINED_TEAM2_PIC] = imageindex_i_ctfj;
  1220.  
  1221.     if (ent->client->resp.id_state)
  1222.         CTFSetIDView(ent);
  1223.     else {
  1224.         ent->client->ps.stats[STAT_CTF_ID_VIEW] = 0;
  1225.         ent->client->ps.stats[STAT_CTF_ID_VIEW_COLOR] = 0;
  1226.     }
  1227. }
  1228.  
  1229. /*------------------------------------------------------------------------*/
  1230.  
  1231. /*QUAKED info_player_team1 (1 0 0) (-16 -16 -24) (16 16 32)
  1232. potential team1 spawning position for ctf games
  1233. */
  1234. void SP_info_player_team1(edict_t *self)
  1235. {
  1236. }
  1237.  
  1238. /*QUAKED info_player_team2 (0 0 1) (-16 -16 -24) (16 16 32)
  1239. potential team2 spawning position for ctf games
  1240. */
  1241. void SP_info_player_team2(edict_t *self)
  1242. {
  1243. }
  1244.  
  1245.  
  1246. /*------------------------------------------------------------------------*/
  1247. /* GRAPPLE                                                                  */
  1248. /*------------------------------------------------------------------------*/
  1249.  
  1250. // ent is player
  1251. void CTFPlayerResetGrapple(edict_t *ent)
  1252. {
  1253.     if (ent->client && ent->client->ctf_grapple)
  1254.         CTFResetGrapple(ent->client->ctf_grapple);
  1255. }
  1256.  
  1257. // self is grapple, not player
  1258. void CTFResetGrapple(edict_t *self)
  1259. {
  1260.     if (self->owner->client->ctf_grapple) {
  1261.         float volume = 1.0;
  1262.         gclient_t *cl;
  1263.  
  1264.         if (self->owner->client->silencer_shots)
  1265.             volume = 0.2;
  1266.  
  1267.         gi.sound (self->owner, CHAN_RELIABLE+CHAN_WEAPON, gi.soundindex("weapons/grapple/grreset.wav"), volume, ATTN_NORM, 0);
  1268.         cl = self->owner->client;
  1269.         cl->ctf_grapple = NULL;
  1270.         cl->ctf_grapplereleasetime = level.time;
  1271.         cl->ctf_grapplestate = CTF_GRAPPLE_STATE_FLY; // we're firing, not on hook
  1272.         cl->ps.pmove.pm_flags &= ~PMF_NO_PREDICTION;
  1273.         G_FreeEdict(self);
  1274.     }
  1275. }
  1276.  
  1277. void CTFGrappleTouch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf)
  1278. {
  1279.     float volume = 1.0;
  1280.  
  1281.     if (other == self->owner)
  1282.         return;
  1283.  
  1284.     if (self->owner->client->ctf_grapplestate != CTF_GRAPPLE_STATE_FLY)
  1285.         return;
  1286.  
  1287.     if (surf && (surf->flags & SURF_SKY))
  1288.     {
  1289.         CTFResetGrapple(self);
  1290.         return;
  1291.     }
  1292.  
  1293.     VectorCopy(vec3_origin, self->velocity);
  1294.  
  1295.     PlayerNoise(self->owner, self->s.origin, PNOISE_IMPACT);
  1296.  
  1297.     if (other->takedamage) {
  1298.         T_Damage (other, self, self->owner, self->velocity, self->s.origin, plane->normal, self->dmg, 1, 0, MOD_GRAPPLE);
  1299.         CTFResetGrapple(self);
  1300.         return;
  1301.     }
  1302.  
  1303.     self->owner->client->ctf_grapplestate = CTF_GRAPPLE_STATE_PULL; // we're on hook
  1304.     self->enemy = other;
  1305.  
  1306.     self->solid = SOLID_NOT;
  1307.  
  1308.     if (self->owner->client->silencer_shots)
  1309.         volume = 0.2;
  1310.  
  1311.     gi.sound (self->owner, CHAN_RELIABLE+CHAN_WEAPON, gi.soundindex("weapons/grapple/grpull.wav"), volume, ATTN_NORM, 0);
  1312.     gi.sound (self, CHAN_WEAPON, gi.soundindex("weapons/grapple/grhit.wav"), volume, ATTN_NORM, 0);
  1313.  
  1314.     gi.WriteByte (svc_temp_entity);
  1315.     gi.WriteByte (TE_SPARKS);
  1316.     gi.WritePosition (self->s.origin);
  1317.     if (!plane)
  1318.         gi.WriteDir (vec3_origin);
  1319.     else
  1320.         gi.WriteDir (plane->normal);
  1321.     gi.multicast (self->s.origin, MULTICAST_PVS);
  1322. }
  1323.  
  1324. // draw beam between grapple and self
  1325. void CTFGrappleDrawCable(edict_t *self)
  1326. {
  1327.     vec3_t    offset, start, end, f, r;
  1328.     vec3_t    dir;
  1329.     float    distance;
  1330.  
  1331.     AngleVectors (self->owner->client->v_angle, f, r, NULL);
  1332.     VectorSet(offset, 16, 16, self->owner->viewheight-8);
  1333.     P_ProjectSource (self->owner->client, self->owner->s.origin, offset, f, r, start);
  1334.  
  1335.     VectorSubtract(start, self->owner->s.origin, offset);
  1336.  
  1337.     VectorSubtract (start, self->s.origin, dir);
  1338.     distance = VectorLength(dir);
  1339.     // don't draw cable if close
  1340.     if (distance < 64)
  1341.         return;
  1342.  
  1343. #if 0
  1344.     if (distance > 256)
  1345.         return;
  1346.  
  1347.     // check for min/max pitch
  1348.     vectoangles (dir, angles);
  1349.     if (angles[0] < -180)
  1350.         angles[0] += 360;
  1351.     if (fabs(angles[0]) > 45)
  1352.         return;
  1353.  
  1354.     trace_t    tr; //!!
  1355.  
  1356.     tr = gi.trace (start, NULL, NULL, self->s.origin, self, MASK_SHOT);
  1357.     if (tr.ent != self) {
  1358.         CTFResetGrapple(self);
  1359.         return;
  1360.     }
  1361. #endif
  1362.  
  1363.     // adjust start for beam origin being in middle of a segment
  1364. //    VectorMA (start, 8, f, start);
  1365.  
  1366.     VectorCopy (self->s.origin, end);
  1367.     // adjust end z for end spot since the monster is currently dead
  1368. //    end[2] = self->absmin[2] + self->size[2] / 2;
  1369.  
  1370.     gi.WriteByte (svc_temp_entity);
  1371. #if 1 //def USE_GRAPPLE_CABLE
  1372.     gi.WriteByte (TE_GRAPPLE_CABLE);
  1373.     gi.WriteShort (self->owner - g_edicts);
  1374.     gi.WritePosition (self->owner->s.origin);
  1375.     gi.WritePosition (end);
  1376.     gi.WritePosition (offset);
  1377. #else
  1378.     gi.WriteByte (TE_MEDIC_CABLE_ATTACK);
  1379.     gi.WriteShort (self - g_edicts);
  1380.     gi.WritePosition (end);
  1381.     gi.WritePosition (start);
  1382. #endif
  1383.     gi.multicast (self->s.origin, MULTICAST_PVS);
  1384. }
  1385.  
  1386. void SV_AddGravity (edict_t *ent);
  1387.  
  1388. // pull the player toward the grapple
  1389. void CTFGrapplePull(edict_t *self)
  1390. {
  1391.     vec3_t hookdir, v;
  1392.     float vlen;
  1393.  
  1394.     if (strcmp(self->owner->client->pers.weapon->classname, "weapon_grapple") == 0 &&
  1395.         !self->owner->client->newweapon &&
  1396.         self->owner->client->weaponstate != WEAPON_FIRING &&
  1397.         self->owner->client->weaponstate != WEAPON_ACTIVATING) {
  1398.         CTFResetGrapple(self);
  1399.         return;
  1400.     }
  1401.  
  1402.     if (self->enemy) {
  1403.         if (self->enemy->solid == SOLID_NOT) {
  1404.             CTFResetGrapple(self);
  1405.             return;
  1406.         }
  1407.         if (self->enemy->solid == SOLID_BBOX) {
  1408.             VectorScale(self->enemy->size, 0.5, v);
  1409.             VectorAdd(v, self->enemy->s.origin, v);
  1410.             VectorAdd(v, self->enemy->mins, self->s.origin);
  1411.             gi.linkentity (self);
  1412.         } else
  1413.             VectorCopy(self->enemy->velocity, self->velocity);
  1414.         if (self->enemy->takedamage &&
  1415.             !CheckTeamDamage (self->enemy, self->owner)) {
  1416.             float volume = 1.0;
  1417.  
  1418.             if (self->owner->client->silencer_shots)
  1419.                 volume = 0.2;
  1420.  
  1421.             T_Damage (self->enemy, self, self->owner, self->velocity, self->s.origin, vec3_origin, 1, 1, 0, MOD_GRAPPLE);
  1422.             gi.sound (self, CHAN_WEAPON, gi.soundindex("weapons/grapple/grhurt.wav"), volume, ATTN_NORM, 0);
  1423.         }
  1424.         if (self->enemy->deadflag) { // he died
  1425.             CTFResetGrapple(self);
  1426.             return;
  1427.         }
  1428.     }
  1429.  
  1430.     CTFGrappleDrawCable(self);
  1431.  
  1432.     if (self->owner->client->ctf_grapplestate > CTF_GRAPPLE_STATE_FLY) {
  1433.         // pull player toward grapple
  1434.         // this causes icky stuff with prediction, we need to extend
  1435.         // the prediction layer to include two new fields in the player
  1436.         // move stuff: a point and a velocity.  The client should add
  1437.         // that velociy in the direction of the point
  1438.         vec3_t forward, up;
  1439.  
  1440.         AngleVectors (self->owner->client->v_angle, forward, NULL, up);
  1441.         VectorCopy(self->owner->s.origin, v);
  1442.         v[2] += self->owner->viewheight;
  1443.         VectorSubtract (self->s.origin, v, hookdir);
  1444.  
  1445.         vlen = VectorLength(hookdir);
  1446.  
  1447.         if (self->owner->client->ctf_grapplestate == CTF_GRAPPLE_STATE_PULL &&
  1448.             vlen < 64) {
  1449.             float volume = 1.0;
  1450.  
  1451.             if (self->owner->client->silencer_shots)
  1452.                 volume = 0.2;
  1453.  
  1454.             self->owner->client->ps.pmove.pm_flags |= PMF_NO_PREDICTION;
  1455.             gi.sound (self->owner, CHAN_RELIABLE+CHAN_WEAPON, gi.soundindex("weapons/grapple/grhang.wav"), volume, ATTN_NORM, 0);
  1456.             self->owner->client->ctf_grapplestate = CTF_GRAPPLE_STATE_HANG;
  1457.         }
  1458.  
  1459.         VectorNormalize (hookdir);
  1460.         VectorScale(hookdir, CTF_GRAPPLE_PULL_SPEED, hookdir);
  1461.         VectorCopy(hookdir, self->owner->velocity);
  1462.         SV_AddGravity(self->owner);
  1463.     }
  1464. }
  1465.  
  1466. void CTFFireGrapple (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, int effect)
  1467. {
  1468.     edict_t    *grapple;
  1469.     trace_t    tr;
  1470.  
  1471.     VectorNormalize (dir);
  1472.  
  1473.     grapple = G_Spawn();
  1474.     VectorCopy (start, grapple->s.origin);
  1475.     VectorCopy (start, grapple->s.old_origin);
  1476.     vectoangles (dir, grapple->s.angles);
  1477.     VectorScale (dir, speed, grapple->velocity);
  1478.     grapple->movetype = MOVETYPE_FLYMISSILE;
  1479.     grapple->clipmask = MASK_SHOT;
  1480.     grapple->solid = SOLID_BBOX;
  1481.     grapple->s.effects |= effect;
  1482.     VectorClear (grapple->mins);
  1483.     VectorClear (grapple->maxs);
  1484.     grapple->s.modelindex = gi.modelindex ("models/weapons/grapple/hook/tris.md2");
  1485. //    grapple->s.sound = gi.soundindex ("misc/lasfly.wav");
  1486.     grapple->owner = self;
  1487.     grapple->touch = CTFGrappleTouch;
  1488. //    grapple->nextthink = level.time + FRAMETIME;
  1489. //    grapple->think = CTFGrappleThink;
  1490.     grapple->dmg = damage;
  1491.     self->client->ctf_grapple = grapple;
  1492.     self->client->ctf_grapplestate = CTF_GRAPPLE_STATE_FLY; // we're firing, not on hook
  1493.     gi.linkentity (grapple);
  1494.  
  1495.     tr = gi.trace (self->s.origin, NULL, NULL, grapple->s.origin, grapple, MASK_SHOT);
  1496.     if (tr.fraction < 1.0)
  1497.     {
  1498.         VectorMA (grapple->s.origin, -10, dir, grapple->s.origin);
  1499.         grapple->touch (grapple, tr.ent, NULL, NULL);
  1500.     }
  1501. }    
  1502.  
  1503. void CTFGrappleFire (edict_t *ent, vec3_t g_offset, int damage, int effect)
  1504. {
  1505.     vec3_t    forward, right;
  1506.     vec3_t    start;
  1507.     vec3_t    offset;
  1508.     float volume = 1.0;
  1509.  
  1510.     if (ent->client->ctf_grapplestate > CTF_GRAPPLE_STATE_FLY)
  1511.         return; // it's already out
  1512.  
  1513.     AngleVectors (ent->client->v_angle, forward, right, NULL);
  1514. //    VectorSet(offset, 24, 16, ent->viewheight-8+2);
  1515.     VectorSet(offset, 24, 8, ent->viewheight-8+2);
  1516.     VectorAdd (offset, g_offset, offset);
  1517.     P_ProjectSource (ent->client, ent->s.origin, offset, forward, right, start);
  1518.  
  1519.     VectorScale (forward, -2, ent->client->kick_origin);
  1520.     ent->client->kick_angles[0] = -1;
  1521.  
  1522.     if (ent->client->silencer_shots)
  1523.         volume = 0.2;
  1524.  
  1525.     gi.sound (ent, CHAN_RELIABLE+CHAN_WEAPON, gi.soundindex("weapons/grapple/grfire.wav"), volume, ATTN_NORM, 0);
  1526.     CTFFireGrapple (ent, start, forward, damage, CTF_GRAPPLE_SPEED, effect);
  1527.  
  1528. #if 0
  1529.     // send muzzle flash
  1530.     gi.WriteByte (svc_muzzleflash);
  1531.     gi.WriteShort (ent-g_edicts);
  1532.     gi.WriteByte (MZ_BLASTER);
  1533.     gi.multicast (ent->s.origin, MULTICAST_PVS);
  1534. #endif
  1535.  
  1536.     PlayerNoise(ent, start, PNOISE_WEAPON);
  1537. }
  1538.  
  1539.  
  1540. void CTFWeapon_Grapple_Fire (edict_t *ent)
  1541. {
  1542.     int        damage;
  1543.  
  1544.     damage = 10;
  1545.     CTFGrappleFire (ent, vec3_origin, damage, 0);
  1546.     ent->client->ps.gunframe++;
  1547. }
  1548.  
  1549. void CTFWeapon_Grapple (edict_t *ent)
  1550. {
  1551.     static int    pause_frames[]    = {10, 18, 27, 0};
  1552.     static int    fire_frames[]    = {6, 0};
  1553.     int prevstate;
  1554.  
  1555.     // if the the attack button is still down, stay in the firing frame
  1556.     if ((ent->client->buttons & BUTTON_ATTACK) && 
  1557.         ent->client->weaponstate == WEAPON_FIRING &&
  1558.         ent->client->ctf_grapple)
  1559.         ent->client->ps.gunframe = 9;
  1560.  
  1561.     if (!(ent->client->buttons & BUTTON_ATTACK) && 
  1562.         ent->client->ctf_grapple) {
  1563.         CTFResetGrapple(ent->client->ctf_grapple);
  1564.         if (ent->client->weaponstate == WEAPON_FIRING)
  1565.             ent->client->weaponstate = WEAPON_READY;
  1566.     }
  1567.  
  1568.  
  1569.     if (ent->client->newweapon && 
  1570.         ent->client->ctf_grapplestate > CTF_GRAPPLE_STATE_FLY &&
  1571.         ent->client->weaponstate == WEAPON_FIRING) {
  1572.         // he wants to change weapons while grappled
  1573.         ent->client->weaponstate = WEAPON_DROPPING;
  1574.         ent->client->ps.gunframe = 32;
  1575.     }
  1576.  
  1577.     prevstate = ent->client->weaponstate;
  1578.     Weapon_Generic (ent, 5, 9, 31, 36, pause_frames, fire_frames, 
  1579.         CTFWeapon_Grapple_Fire);
  1580.  
  1581.     // if we just switched back to grapple, immediately go to fire frame
  1582.     if (prevstate == WEAPON_ACTIVATING &&
  1583.         ent->client->weaponstate == WEAPON_READY &&
  1584.         ent->client->ctf_grapplestate > CTF_GRAPPLE_STATE_FLY) {
  1585.         if (!(ent->client->buttons & BUTTON_ATTACK))
  1586.             ent->client->ps.gunframe = 9;
  1587.         else
  1588.             ent->client->ps.gunframe = 5;
  1589.         ent->client->weaponstate = WEAPON_FIRING;
  1590.     }
  1591. }
  1592.  
  1593. void CTFTeam_f (edict_t *ent)
  1594. {
  1595.     char *t, *s;
  1596.     int desired_team;
  1597.  
  1598.     t = gi.args();
  1599.     if (!*t) {
  1600.         gi.cprintf(ent, PRINT_HIGH, "You are on the %s team.\n",
  1601.             CTFTeamName(ent->client->resp.ctf_team));
  1602.         return;
  1603.     }
  1604.  
  1605.     if (ctfgame.match > MATCH_SETUP) {
  1606.         gi.cprintf(ent, PRINT_HIGH, "Can't change teams in a match.\n");
  1607.         return;
  1608.     }
  1609.  
  1610.     if (Q_stricmp(t, "red") == 0)
  1611.         desired_team = CTF_TEAM1;
  1612.     else if (Q_stricmp(t, "blue") == 0)
  1613.         desired_team = CTF_TEAM2;
  1614.     else {
  1615.         gi.cprintf(ent, PRINT_HIGH, "Unknown team %s.\n", t);
  1616.         return;
  1617.     }
  1618.  
  1619.     if (ent->client->resp.ctf_team == desired_team) {
  1620.         gi.cprintf(ent, PRINT_HIGH, "You are already on the %s team.\n",
  1621.             CTFTeamName(ent->client->resp.ctf_team));
  1622.         return;
  1623.     }
  1624.  
  1625. ////
  1626.     ent->svflags = 0;
  1627.     ent->flags &= ~FL_GODMODE;
  1628.     ent->client->resp.ctf_team = desired_team;
  1629.     ent->client->resp.ctf_state = 0;
  1630.     s = Info_ValueForKey (ent->client->pers.userinfo, "skin");
  1631.     CTFAssignSkin(ent, s);
  1632.  
  1633.     if (ent->solid == SOLID_NOT) { // spectator
  1634.         PutClientInServer (ent);
  1635.         // add a teleportation effect
  1636.         ent->s.event = EV_PLAYER_TELEPORT;
  1637.         // hold in place briefly
  1638.         ent->client->ps.pmove.pm_flags = PMF_TIME_TELEPORT;
  1639.         ent->client->ps.pmove.pm_time = 14;
  1640.         gi.bprintf(PRINT_HIGH, "%s joined the %s team.\n",
  1641.             ent->client->pers.netname, CTFTeamName(desired_team));
  1642.         return;
  1643.     }
  1644.  
  1645.     ent->health = 0;
  1646.     player_die (ent, ent, ent, 100000, vec3_origin);
  1647.     // don't even bother waiting for death frames
  1648.     ent->deadflag = DEAD_DEAD;
  1649.     respawn (ent);
  1650.  
  1651.     ent->client->resp.score = 0;
  1652.  
  1653.     gi.bprintf(PRINT_HIGH, "%s changed to the %s team.\n",
  1654.         ent->client->pers.netname, CTFTeamName(desired_team));
  1655. }
  1656.  
  1657. /*
  1658. ==================
  1659. CTFScoreboardMessage
  1660. ==================
  1661. */
  1662. void CTFScoreboardMessage (edict_t *ent, edict_t *killer)
  1663. {
  1664.     char    entry[1024];
  1665.     char    string[1400];
  1666.     int        len;
  1667.     int        i, j, k, n;
  1668.     int        sorted[2][MAX_CLIENTS];
  1669.     int        sortedscores[2][MAX_CLIENTS];
  1670.     int        score, total[2], totalscore[2];
  1671.     int        last[2];
  1672.     gclient_t    *cl;
  1673.     edict_t        *cl_ent;
  1674.     int team;
  1675.     int maxsize = 1000;
  1676.  
  1677.     // sort the clients by team and score
  1678.     total[0] = total[1] = 0;
  1679.     last[0] = last[1] = 0;
  1680.     totalscore[0] = totalscore[1] = 0;
  1681.     for (i=0 ; i<game.maxclients ; i++)
  1682.     {
  1683.         cl_ent = g_edicts + 1 + i;
  1684.         if (!cl_ent->inuse)
  1685.             continue;
  1686.         if (game.clients[i].resp.ctf_team == CTF_TEAM1)
  1687.             team = 0;
  1688.         else if (game.clients[i].resp.ctf_team == CTF_TEAM2)
  1689.             team = 1;
  1690.         else
  1691.             continue; // unknown team?
  1692.  
  1693.         score = game.clients[i].resp.score;
  1694.         for (j=0 ; j<total[team] ; j++)
  1695.         {
  1696.             if (score > sortedscores[team][j])
  1697.                 break;
  1698.         }
  1699.         for (k=total[team] ; k>j ; k--)
  1700.         {
  1701.             sorted[team][k] = sorted[team][k-1];
  1702.             sortedscores[team][k] = sortedscores[team][k-1];
  1703.         }
  1704.         sorted[team][j] = i;
  1705.         sortedscores[team][j] = score;
  1706.         totalscore[team] += score;
  1707.         total[team]++;
  1708.     }
  1709.  
  1710.     // print level name and exit rules
  1711.     // add the clients in sorted order
  1712.     *string = 0;
  1713.     len = 0;
  1714.  
  1715.     // team one
  1716.     sprintf(string, "if 24 xv 8 yv 8 pic 24 endif "
  1717.         "xv 40 yv 28 string \"%4d/%-3d\" "
  1718.         "xv 98 yv 12 num 2 18 "
  1719.         "if 25 xv 168 yv 8 pic 25 endif "
  1720.         "xv 200 yv 28 string \"%4d/%-3d\" "
  1721.         "xv 256 yv 12 num 2 20 ",
  1722.         totalscore[0], total[0],
  1723.         totalscore[1], total[1]);
  1724.     len = strlen(string);
  1725.  
  1726.     for (i=0 ; i<16 ; i++)
  1727.     {
  1728.         if (i >= total[0] && i >= total[1])
  1729.             break; // we're done
  1730.  
  1731. #if 0 //ndef NEW_SCORE
  1732.         // set up y
  1733.         sprintf(entry, "yv %d ", 42 + i * 8);
  1734.         if (maxsize - len > strlen(entry)) {
  1735.             strcat(string, entry);
  1736.             len = strlen(string);
  1737.         }
  1738. #else
  1739.         *entry = 0;
  1740. #endif
  1741.  
  1742.         // left side
  1743.         if (i < total[0]) {
  1744.             cl = &game.clients[sorted[0][i]];
  1745.             cl_ent = g_edicts + 1 + sorted[0][i];
  1746.  
  1747. #if 0 //ndef NEW_SCORE
  1748.             sprintf(entry+strlen(entry),
  1749.             "xv 0 %s \"%3d %3d %-12.12s\" ",
  1750.             (cl_ent == ent) ? "string2" : "string",
  1751.             cl->resp.score, 
  1752.             (cl->ping > 999) ? 999 : cl->ping, 
  1753.             cl->pers.netname);
  1754.  
  1755.             if (cl_ent->client->pers.inventory[ITEM_INDEX(flag2_item)])
  1756.                 strcat(entry, "xv 56 picn sbfctf2 ");
  1757. #else
  1758.             sprintf(entry+strlen(entry),
  1759.                 "ctf 0 %d %d %d %d ",
  1760.                 42 + i * 8,
  1761.                 sorted[0][i],
  1762.                 cl->resp.score,
  1763.                 cl->ping > 999 ? 999 : cl->ping);
  1764.  
  1765.             if (cl_ent->client->pers.inventory[ITEM_INDEX(flag2_item)])
  1766.                 sprintf(entry + strlen(entry), "xv 56 yv %d picn sbfctf2 ",
  1767.                     42 + i * 8);
  1768. #endif
  1769.  
  1770.             if (maxsize - len > strlen(entry)) {
  1771.                 strcat(string, entry);
  1772.                 len = strlen(string);
  1773.                 last[0] = i;
  1774.             }
  1775.         }
  1776.  
  1777.         // right side
  1778.         if (i < total[1]) {
  1779.             cl = &game.clients[sorted[1][i]];
  1780.             cl_ent = g_edicts + 1 + sorted[1][i];
  1781.  
  1782. #if 0 //ndef NEW_SCORE
  1783.             sprintf(entry+strlen(entry),
  1784.             "xv 160 %s \"%3d %3d %-12.12s\" ",
  1785.             (cl_ent == ent) ? "string2" : "string",
  1786.             cl->resp.score, 
  1787.             (cl->ping > 999) ? 999 : cl->ping, 
  1788.             cl->pers.netname);
  1789.  
  1790.             if (cl_ent->client->pers.inventory[ITEM_INDEX(flag1_item)])
  1791.                 strcat(entry, "xv 216 picn sbfctf1 ");
  1792.  
  1793. #else
  1794.  
  1795.             sprintf(entry+strlen(entry),
  1796.                 "ctf 160 %d %d %d %d ",
  1797.                 42 + i * 8,
  1798.                 sorted[1][i],
  1799.                 cl->resp.score,
  1800.                 cl->ping > 999 ? 999 : cl->ping);
  1801.  
  1802.             if (cl_ent->client->pers.inventory[ITEM_INDEX(flag1_item)])
  1803.                 sprintf(entry + strlen(entry), "xv 216 yv %d picn sbfctf1 ",
  1804.                     42 + i * 8);
  1805. #endif
  1806.             if (maxsize - len > strlen(entry)) {
  1807.                 strcat(string, entry);
  1808.                 len = strlen(string);
  1809.                 last[1] = i;
  1810.             }
  1811.         }
  1812.     }
  1813.  
  1814.     // put in spectators if we have enough room
  1815.     if (last[0] > last[1])
  1816.         j = last[0];
  1817.     else
  1818.         j = last[1];
  1819.     j = (j + 2) * 8 + 42;
  1820.  
  1821.     k = n = 0;
  1822.     if (maxsize - len > 50) {
  1823.         for (i = 0; i < maxclients->value; i++) {
  1824.             cl_ent = g_edicts + 1 + i;
  1825.             cl = &game.clients[i];
  1826.             if (!cl_ent->inuse ||
  1827.                 cl_ent->solid != SOLID_NOT ||
  1828.                 cl_ent->client->resp.ctf_team != CTF_NOTEAM)
  1829.                 continue;
  1830.  
  1831.             if (!k) {
  1832.                 k = 1;
  1833.                 sprintf(entry, "xv 0 yv %d string2 \"Spectators\" ", j);
  1834.                 strcat(string, entry);
  1835.                 len = strlen(string);
  1836.                 j += 8;
  1837.             }
  1838.  
  1839.             sprintf(entry+strlen(entry),
  1840.                 "ctf %d %d %d %d %d ",
  1841.                 (n & 1) ? 160 : 0, // x
  1842.                 j, // y
  1843.                 i, // playernum
  1844.                 cl->resp.score,
  1845.                 cl->ping > 999 ? 999 : cl->ping);
  1846.             if (maxsize - len > strlen(entry)) {
  1847.                 strcat(string, entry);
  1848.                 len = strlen(string);
  1849.             }
  1850.             
  1851.             if (n & 1)
  1852.                 j += 8;
  1853.             n++;
  1854.         }
  1855.     }
  1856.  
  1857.     if (total[0] - last[0] > 1) // couldn't fit everyone
  1858.         sprintf(string + strlen(string), "xv 8 yv %d string \"..and %d more\" ",
  1859.             42 + (last[0]+1)*8, total[0] - last[0] - 1);
  1860.     if (total[1] - last[1] > 1) // couldn't fit everyone
  1861.         sprintf(string + strlen(string), "xv 168 yv %d string \"..and %d more\" ",
  1862.             42 + (last[1]+1)*8, total[1] - last[1] - 1);
  1863.  
  1864.     gi.WriteByte (svc_layout);
  1865.     gi.WriteString (string);
  1866. }
  1867.  
  1868. /*------------------------------------------------------------------------*/
  1869. /* TECH                                                                      */
  1870. /*------------------------------------------------------------------------*/
  1871.  
  1872. void CTFHasTech(edict_t *who)
  1873. {
  1874.     if (level.time - who->client->ctf_lasttechmsg > 2) {
  1875.         gi.centerprintf(who, "You already have a TECH powerup.");
  1876.         who->client->ctf_lasttechmsg = level.time;
  1877.     }
  1878. }
  1879.  
  1880. gitem_t *CTFWhat_Tech(edict_t *ent)
  1881. {
  1882.     gitem_t *tech;
  1883.     int i;
  1884.  
  1885.     i = 0;
  1886.     while (tnames[i]) {
  1887.         if ((tech = FindItemByClassname(tnames[i])) != NULL &&
  1888.             ent->client->pers.inventory[ITEM_INDEX(tech)]) {
  1889.             return tech;
  1890.         }
  1891.         i++;
  1892.     }
  1893.     return NULL;
  1894. }
  1895.  
  1896. qboolean CTFPickup_Tech (edict_t *ent, edict_t *other)
  1897. {
  1898.     gitem_t *tech;
  1899.     int i;
  1900.  
  1901.     i = 0;
  1902.     while (tnames[i]) {
  1903.         if ((tech = FindItemByClassname(tnames[i])) != NULL &&
  1904.             other->client->pers.inventory[ITEM_INDEX(tech)]) {
  1905.             CTFHasTech(other);
  1906.             return false; // has this one
  1907.         }
  1908.         i++;
  1909.     }
  1910.     
  1911.     // client only gets one tech
  1912.     other->client->pers.inventory[ITEM_INDEX(ent->item)]++;
  1913.     other->client->ctf_regentime = level.time;
  1914.     return true;
  1915. }
  1916.  
  1917. static void SpawnTech(gitem_t *item, edict_t *spot);
  1918.  
  1919. static edict_t *FindTechSpawn(void)
  1920. {
  1921.     edict_t *spot = NULL;
  1922.     int i = rand() % 16;
  1923.  
  1924.     while (i--)
  1925.         spot = G_Find (spot, FOFS(classname), "info_player_deathmatch");
  1926.     if (!spot)
  1927.         spot = G_Find (spot, FOFS(classname), "info_player_deathmatch");
  1928.     return spot;
  1929. }
  1930.  
  1931. static void TechThink(edict_t *tech)
  1932. {
  1933.     edict_t *spot;
  1934.  
  1935.     if ((spot = FindTechSpawn()) != NULL) {
  1936.         SpawnTech(tech->item, spot);
  1937.         G_FreeEdict(tech);
  1938.     } else {
  1939.         tech->nextthink = level.time + CTF_TECH_TIMEOUT;
  1940.         tech->think = TechThink;
  1941.     }
  1942. }
  1943.  
  1944. void CTFDrop_Tech(edict_t *ent, gitem_t *item)
  1945. {
  1946.     edict_t *tech;
  1947.  
  1948.     tech = Drop_Item(ent, item);
  1949.     tech->nextthink = level.time + CTF_TECH_TIMEOUT;
  1950.     tech->think = TechThink;
  1951.     ent->client->pers.inventory[ITEM_INDEX(item)] = 0;
  1952. }
  1953.  
  1954. void CTFDeadDropTech(edict_t *ent)
  1955. {
  1956.     gitem_t *tech;
  1957.     edict_t *dropped;
  1958.     int i;
  1959.  
  1960.     i = 0;
  1961.     while (tnames[i]) {
  1962.         if ((tech = FindItemByClassname(tnames[i])) != NULL &&
  1963.             ent->client->pers.inventory[ITEM_INDEX(tech)]) {
  1964.             dropped = Drop_Item(ent, tech);
  1965.             // hack the velocity to make it bounce random
  1966.             dropped->velocity[0] = (rand() % 600) - 300;
  1967.             dropped->velocity[1] = (rand() % 600) - 300;
  1968.             dropped->nextthink = level.time + CTF_TECH_TIMEOUT;
  1969.             dropped->think = TechThink;
  1970.             dropped->owner = NULL;
  1971.             ent->client->pers.inventory[ITEM_INDEX(tech)] = 0;
  1972.         }
  1973.         i++;
  1974.     }
  1975. }
  1976.  
  1977. static void SpawnTech(gitem_t *item, edict_t *spot)
  1978. {
  1979.     edict_t    *ent;
  1980.     vec3_t    forward, right;
  1981.     vec3_t  angles;
  1982.  
  1983.     ent = G_Spawn();
  1984.  
  1985.     ent->classname = item->classname;
  1986.     ent->item = item;
  1987.     ent->spawnflags = DROPPED_ITEM;
  1988.     ent->s.effects = item->world_model_flags;
  1989.     ent->s.renderfx = RF_GLOW;
  1990.     VectorSet (ent->mins, -15, -15, -15);
  1991.     VectorSet (ent->maxs, 15, 15, 15);
  1992.     gi.setmodel (ent, ent->item->world_model);
  1993.     ent->solid = SOLID_TRIGGER;
  1994.     ent->movetype = MOVETYPE_TOSS;  
  1995.     ent->touch = Touch_Item;
  1996.     ent->owner = ent;
  1997.  
  1998.     angles[0] = 0;
  1999.     angles[1] = rand() % 360;
  2000.     angles[2] = 0;
  2001.  
  2002.     AngleVectors (angles, forward, right, NULL);
  2003.     VectorCopy (spot->s.origin, ent->s.origin);
  2004.     ent->s.origin[2] += 16;
  2005.     VectorScale (forward, 100, ent->velocity);
  2006.     ent->velocity[2] = 300;
  2007.  
  2008.     ent->nextthink = level.time + CTF_TECH_TIMEOUT;
  2009.     ent->think = TechThink;
  2010.  
  2011.     gi.linkentity (ent);
  2012. }
  2013.  
  2014. static void SpawnTechs(edict_t *ent)
  2015. {
  2016.     gitem_t *tech;
  2017.     edict_t *spot;
  2018.     int i;
  2019.  
  2020.     i = 0;
  2021.     while (tnames[i]) {
  2022.         if ((tech = FindItemByClassname(tnames[i])) != NULL &&
  2023.             (spot = FindTechSpawn()) != NULL)
  2024.             SpawnTech(tech, spot);
  2025.         i++;
  2026.     }
  2027.     if (ent)
  2028.         G_FreeEdict(ent);
  2029. }
  2030.  
  2031. // frees the passed edict!
  2032. void CTFRespawnTech(edict_t *ent)
  2033. {
  2034.     edict_t *spot;
  2035.  
  2036.     if ((spot = FindTechSpawn()) != NULL)
  2037.         SpawnTech(ent->item, spot);
  2038.     G_FreeEdict(ent);
  2039. }
  2040.  
  2041. void CTFSetupTechSpawn(void)
  2042. {
  2043.     edict_t *ent;
  2044.  
  2045.     if (((int)dmflags->value & DF_CTF_NO_TECH))
  2046.         return;
  2047.  
  2048.     ent = G_Spawn();
  2049.     ent->nextthink = level.time + 2;
  2050.     ent->think = SpawnTechs;
  2051. }
  2052.  
  2053. void CTFResetTech(void)
  2054. {
  2055.     edict_t *ent;
  2056.     int i;
  2057.  
  2058.     for (ent = g_edicts + 1, i = 1; i < globals.num_edicts; i++, ent++) {
  2059.         if (ent->inuse)
  2060.             if (ent->item && (ent->item->flags & IT_TECH))
  2061.                 G_FreeEdict(ent);
  2062.     }
  2063.     SpawnTechs(NULL);
  2064. }
  2065.  
  2066. int CTFApplyResistance(edict_t *ent, int dmg)
  2067. {
  2068.     static gitem_t *tech = NULL;
  2069.     float volume = 1.0;
  2070.  
  2071.     if (ent->client && ent->client->silencer_shots)
  2072.         volume = 0.2;
  2073.  
  2074.     if (!tech)
  2075.         tech = FindItemByClassname("item_tech1");
  2076.     if (dmg && tech && ent->client && ent->client->pers.inventory[ITEM_INDEX(tech)]) {
  2077.         // make noise
  2078.            gi.sound(ent, CHAN_VOICE, gi.soundindex("ctf/tech1.wav"), volume, ATTN_NORM, 0);
  2079.         return dmg / 2;
  2080.     }
  2081.     return dmg;
  2082. }
  2083.  
  2084. int CTFApplyStrength(edict_t *ent, int dmg)
  2085. {
  2086.     static gitem_t *tech = NULL;
  2087.  
  2088.     if (!tech)
  2089.         tech = FindItemByClassname("item_tech2");
  2090.     if (dmg && tech && ent->client && ent->client->pers.inventory[ITEM_INDEX(tech)]) {
  2091.         return dmg * 2;
  2092.     }
  2093.     return dmg;
  2094. }
  2095.  
  2096. qboolean CTFApplyStrengthSound(edict_t *ent)
  2097. {
  2098.     static gitem_t *tech = NULL;
  2099.     float volume = 1.0;
  2100.  
  2101.     if (ent->client && ent->client->silencer_shots)
  2102.         volume = 0.2;
  2103.  
  2104.     if (!tech)
  2105.         tech = FindItemByClassname("item_tech2");
  2106.     if (tech && ent->client &&
  2107.         ent->client->pers.inventory[ITEM_INDEX(tech)]) {
  2108.         if (ent->client->ctf_techsndtime < level.time) {
  2109.             ent->client->ctf_techsndtime = level.time + 1;
  2110.             if (ent->client->quad_framenum > level.framenum)
  2111.                 gi.sound(ent, CHAN_VOICE, gi.soundindex("ctf/tech2x.wav"), volume, ATTN_NORM, 0);
  2112.             else
  2113.                 gi.sound(ent, CHAN_VOICE, gi.soundindex("ctf/tech2.wav"), volume, ATTN_NORM, 0);
  2114.         }
  2115.         return true;
  2116.     }
  2117.     return false;
  2118. }
  2119.  
  2120.  
  2121. qboolean CTFApplyHaste(edict_t *ent)
  2122. {
  2123.     static gitem_t *tech = NULL;
  2124.  
  2125.     if (!tech)
  2126.         tech = FindItemByClassname("item_tech3");
  2127.     if (tech && ent->client &&
  2128.         ent->client->pers.inventory[ITEM_INDEX(tech)])
  2129.         return true;
  2130.     return false;
  2131. }
  2132.  
  2133. void CTFApplyHasteSound(edict_t *ent)
  2134. {
  2135.     static gitem_t *tech = NULL;
  2136.     float volume = 1.0;
  2137.  
  2138.     if (ent->client && ent->client->silencer_shots)
  2139.         volume = 0.2;
  2140.  
  2141.     if (!tech)
  2142.         tech = FindItemByClassname("item_tech3");
  2143.     if (tech && ent->client &&
  2144.         ent->client->pers.inventory[ITEM_INDEX(tech)] &&
  2145.         ent->client->ctf_techsndtime < level.time) {
  2146.         ent->client->ctf_techsndtime = level.time + 1;
  2147.         gi.sound(ent, CHAN_VOICE, gi.soundindex("ctf/tech3.wav"), volume, ATTN_NORM, 0);
  2148.     }
  2149. }
  2150.  
  2151. void CTFApplyRegeneration(edict_t *ent)
  2152. {
  2153.     static gitem_t *tech = NULL;
  2154.     qboolean noise = false;
  2155.     gclient_t *client;
  2156.     int index;
  2157.     float volume = 1.0;
  2158.  
  2159.     client = ent->client;
  2160.     if (!client)
  2161.         return;
  2162.  
  2163.     if (ent->client->silencer_shots)
  2164.         volume = 0.2;
  2165.  
  2166.     if (!tech)
  2167.         tech = FindItemByClassname("item_tech4");
  2168.     if (tech && client->pers.inventory[ITEM_INDEX(tech)]) {
  2169.         if (client->ctf_regentime < level.time) {
  2170.             client->ctf_regentime = level.time;
  2171.             if (ent->health < 150) {
  2172.                 ent->health += 5;
  2173.                 if (ent->health > 150)
  2174.                     ent->health = 150;
  2175.                 client->ctf_regentime += 0.5;
  2176.                 noise = true;
  2177.             }
  2178.             index = ArmorIndex (ent);
  2179.             if (index && client->pers.inventory[index] < 150) {
  2180.                 client->pers.inventory[index] += 5;
  2181.                 if (client->pers.inventory[index] > 150)
  2182.                     client->pers.inventory[index] = 150;
  2183.                 client->ctf_regentime += 0.5;
  2184.                 noise = true;
  2185.             }
  2186.         }
  2187.         if (noise && ent->client->ctf_techsndtime < level.time) {
  2188.             ent->client->ctf_techsndtime = level.time + 1;
  2189.             gi.sound(ent, CHAN_VOICE, gi.soundindex("ctf/tech4.wav"), volume, ATTN_NORM, 0);
  2190.         }
  2191.     }
  2192. }
  2193.  
  2194. qboolean CTFHasRegeneration(edict_t *ent)
  2195. {
  2196.     static gitem_t *tech = NULL;
  2197.  
  2198.     if (!tech)
  2199.         tech = FindItemByClassname("item_tech4");
  2200.     if (tech && ent->client &&
  2201.         ent->client->pers.inventory[ITEM_INDEX(tech)])
  2202.         return true;
  2203.     return false;
  2204. }
  2205.  
  2206. /*
  2207. ======================================================================
  2208.  
  2209. SAY_TEAM
  2210.  
  2211. ======================================================================
  2212. */
  2213.  
  2214. // This array is in 'importance order', it indicates what items are
  2215. // more important when reporting their names.
  2216. struct {
  2217.     char *classname;
  2218.     int priority;
  2219. } loc_names[] = 
  2220. {
  2221.     {    "item_flag_team1",            1 },
  2222.     {    "item_flag_team2",            1 },
  2223.     {    "item_quad",                2 }, 
  2224.     {    "item_invulnerability",        2 },
  2225.     {    "weapon_bfg",                3 },
  2226.     {    "weapon_railgun",            4 },
  2227.     {    "weapon_rocketlauncher",    4 },
  2228.     {    "weapon_hyperblaster",        4 },
  2229.     {    "weapon_chaingun",            4 },
  2230.     {    "weapon_grenadelauncher",    4 },
  2231.     {    "weapon_machinegun",        4 },
  2232.     {    "weapon_supershotgun",        4 },
  2233.     {    "weapon_shotgun",            4 },
  2234.     {    "item_power_screen",        5 },
  2235.     {    "item_power_shield",        5 },
  2236.     {    "item_armor_body",            6 },
  2237.     {    "item_armor_combat",        6 },
  2238.     {    "item_armor_jacket",        6 },
  2239.     {    "item_silencer",            7 },
  2240.     {    "item_breather",            7 },
  2241.     {    "item_enviro",                7 },
  2242.     {    "item_adrenaline",            7 },
  2243.     {    "item_bandolier",            8 },
  2244.     {    "item_pack",                8 },
  2245.     { NULL, 0 }
  2246. };
  2247.  
  2248.  
  2249. static void CTFSay_Team_Location(edict_t *who, char *buf)
  2250. {
  2251.     edict_t *what = NULL;
  2252.     edict_t *hot = NULL;
  2253.     float hotdist = 999999, newdist;
  2254.     vec3_t v;
  2255.     int hotindex = 999;
  2256.     int i;
  2257.     gitem_t *item;
  2258.     int nearteam = -1;
  2259.     edict_t *flag1, *flag2;
  2260.     qboolean hotsee = false;
  2261.     qboolean cansee;
  2262.  
  2263.     while ((what = loc_findradius(what, who->s.origin, 1024)) != NULL) {
  2264.         // find what in loc_classnames
  2265.         for (i = 0; loc_names[i].classname; i++)
  2266.             if (strcmp(what->classname, loc_names[i].classname) == 0)
  2267.                 break;
  2268.         if (!loc_names[i].classname)
  2269.             continue;
  2270.         // something we can see get priority over something we can't
  2271.         cansee = loc_CanSee(what, who);
  2272.         if (cansee && !hotsee) {
  2273.             hotsee = true;
  2274.             hotindex = loc_names[i].priority;
  2275.             hot = what;
  2276.             VectorSubtract(what->s.origin, who->s.origin, v);
  2277.             hotdist = VectorLength(v);
  2278.             continue;
  2279.         }
  2280.         // if we can't see this, but we have something we can see, skip it
  2281.         if (hotsee && !cansee)
  2282.             continue;
  2283.         if (hotsee && hotindex < loc_names[i].priority)
  2284.             continue;
  2285.         VectorSubtract(what->s.origin, who->s.origin, v);
  2286.         newdist = VectorLength(v);
  2287.         if (newdist < hotdist || 
  2288.             (cansee && loc_names[i].priority < hotindex)) {
  2289.             hot = what;
  2290.             hotdist = newdist;
  2291.             hotindex = i;
  2292.             hotsee = loc_CanSee(hot, who);
  2293.         }
  2294.     }
  2295.  
  2296.     if (!hot) {
  2297.         strcpy(buf, "nowhere");
  2298.         return;
  2299.     }
  2300.  
  2301.     // we now have the closest item
  2302.     // see if there's more than one in the map, if so
  2303.     // we need to determine what team is closest
  2304.     what = NULL;
  2305.     while ((what = G_Find(what, FOFS(classname), hot->classname)) != NULL) {
  2306.         if (what == hot)
  2307.             continue;
  2308.         // if we are here, there is more than one, find out if hot
  2309.         // is closer to red flag or blue flag
  2310.         if ((flag1 = G_Find(NULL, FOFS(classname), "item_flag_team1")) != NULL &&
  2311.             (flag2 = G_Find(NULL, FOFS(classname), "item_flag_team2")) != NULL) {
  2312.             VectorSubtract(hot->s.origin, flag1->s.origin, v);
  2313.             hotdist = VectorLength(v);
  2314.             VectorSubtract(hot->s.origin, flag2->s.origin, v);
  2315.             newdist = VectorLength(v);
  2316.             if (hotdist < newdist)
  2317.                 nearteam = CTF_TEAM1;
  2318.             else if (hotdist > newdist)
  2319.                 nearteam = CTF_TEAM2;
  2320.         }
  2321.         break;
  2322.     }
  2323.  
  2324.     if ((item = FindItemByClassname(hot->classname)) == NULL) {
  2325.         strcpy(buf, "nowhere");
  2326.         return;
  2327.     }
  2328.  
  2329.     // in water?
  2330.     if (who->waterlevel)
  2331.         strcpy(buf, "in the water ");
  2332.     else
  2333.         *buf = 0;
  2334.  
  2335.     // near or above
  2336.     VectorSubtract(who->s.origin, hot->s.origin, v);
  2337.     if (fabs(v[2]) > fabs(v[0]) && fabs(v[2]) > fabs(v[1]))
  2338.         if (v[2] > 0)
  2339.             strcat(buf, "above ");
  2340.         else
  2341.             strcat(buf, "below ");
  2342.     else
  2343.         strcat(buf, "near ");
  2344.  
  2345.     if (nearteam == CTF_TEAM1)
  2346.         strcat(buf, "the red ");
  2347.     else if (nearteam == CTF_TEAM2)
  2348.         strcat(buf, "the blue ");
  2349.     else
  2350.         strcat(buf, "the ");
  2351.  
  2352.     strcat(buf, item->pickup_name);
  2353. }
  2354.  
  2355. static void CTFSay_Team_Armor(edict_t *who, char *buf)
  2356. {
  2357.     gitem_t        *item;
  2358.     int            index, cells;
  2359.     int            power_armor_type;
  2360.  
  2361.     *buf = 0;
  2362.  
  2363.     power_armor_type = PowerArmorType (who);
  2364.     if (power_armor_type)
  2365.     {
  2366.         cells = who->client->pers.inventory[ITEM_INDEX(FindItem ("cells"))];
  2367.         if (cells)
  2368.             sprintf(buf+strlen(buf), "%s with %i cells ",
  2369.                 (power_armor_type == POWER_ARMOR_SCREEN) ?
  2370.                 "Power Screen" : "Power Shield", cells);
  2371.     }
  2372.  
  2373.     index = ArmorIndex (who);
  2374.     if (index)
  2375.     {
  2376.         item = GetItemByIndex (index);
  2377.         if (item) {
  2378.             if (*buf)
  2379.                 strcat(buf, "and ");
  2380.             sprintf(buf+strlen(buf), "%i units of %s",
  2381.                 who->client->pers.inventory[index], item->pickup_name);
  2382.         }
  2383.     }
  2384.  
  2385.     if (!*buf)
  2386.         strcpy(buf, "no armor");
  2387. }
  2388.  
  2389. static void CTFSay_Team_Health(edict_t *who, char *buf)
  2390. {
  2391.     if (who->health <= 0)
  2392.         strcpy(buf, "dead");
  2393.     else
  2394.         sprintf(buf, "%i health", who->health);
  2395. }
  2396.  
  2397. static void CTFSay_Team_Tech(edict_t *who, char *buf)
  2398. {
  2399.     gitem_t *tech;
  2400.     int i;
  2401.  
  2402.     // see if the player has a tech powerup
  2403.     i = 0;
  2404.     while (tnames[i]) {
  2405.         if ((tech = FindItemByClassname(tnames[i])) != NULL &&
  2406.             who->client->pers.inventory[ITEM_INDEX(tech)]) {
  2407.             sprintf(buf, "the %s", tech->pickup_name);
  2408.             return;
  2409.         }
  2410.         i++;
  2411.     }
  2412.     strcpy(buf, "no powerup");
  2413. }
  2414.  
  2415. static void CTFSay_Team_Weapon(edict_t *who, char *buf)
  2416. {
  2417.     if (who->client->pers.weapon)
  2418.         strcpy(buf, who->client->pers.weapon->pickup_name);
  2419.     else
  2420.         strcpy(buf, "none");
  2421. }
  2422.  
  2423. static void CTFSay_Team_Sight(edict_t *who, char *buf)
  2424. {
  2425.     int i;
  2426.     edict_t *targ;
  2427.     int n = 0;
  2428.     char s[1024];
  2429.     char s2[1024];
  2430.  
  2431.     *s = *s2 = 0;
  2432.     for (i = 1; i <= maxclients->value; i++) {
  2433.         targ = g_edicts + i;
  2434.         if (!targ->inuse || 
  2435.             targ == who ||
  2436.             !loc_CanSee(targ, who))
  2437.             continue;
  2438.         if (*s2) {
  2439.             if (strlen(s) + strlen(s2) + 3 < sizeof(s)) {
  2440.                 if (n)
  2441.                     strcat(s, ", ");
  2442.                 strcat(s, s2);
  2443.                 *s2 = 0;
  2444.             }
  2445.             n++;
  2446.         }
  2447.         strcpy(s2, targ->client->pers.netname);
  2448.     }
  2449.     if (*s2) {
  2450.         if (strlen(s) + strlen(s2) + 6 < sizeof(s)) {
  2451.             if (n)
  2452.                 strcat(s, " and ");
  2453.             strcat(s, s2);
  2454.         }
  2455.         strcpy(buf, s);
  2456.     } else
  2457.         strcpy(buf, "no one");
  2458. }
  2459.  
  2460. void CTFSay_Team(edict_t *who, char *msg)
  2461. {
  2462.     char outmsg[256];
  2463.     char buf[256];
  2464.     int i;
  2465.     char *p;
  2466.     edict_t *cl_ent;
  2467.  
  2468.     if (CheckFlood(who))
  2469.         return;
  2470.  
  2471.     outmsg[0] = 0;
  2472.  
  2473.     if (*msg == '\"') {
  2474.         msg[strlen(msg) - 1] = 0;
  2475.         msg++;
  2476.     }
  2477.  
  2478.     for (p = outmsg; *msg && (p - outmsg) < sizeof(outmsg) - 2; msg++) {
  2479.         if (*msg == '%') {
  2480.             switch (*++msg) {
  2481.                 case 'l' :
  2482.                 case 'L' :
  2483.                     CTFSay_Team_Location(who, buf);
  2484.                     if (strlen(buf) + (p - outmsg) < sizeof(outmsg) - 2) {
  2485.                         strcpy(p, buf);
  2486.                         p += strlen(buf);
  2487.                     }
  2488.                     break;
  2489.                 case 'a' :
  2490.                 case 'A' :
  2491.                     CTFSay_Team_Armor(who, buf);
  2492.                     if (strlen(buf) + (p - outmsg) < sizeof(outmsg) - 2) {
  2493.                         strcpy(p, buf);
  2494.                         p += strlen(buf);
  2495.                     }
  2496.                     break;
  2497.                 case 'h' :
  2498.                 case 'H' :
  2499.                     CTFSay_Team_Health(who, buf);
  2500.                     if (strlen(buf) + (p - outmsg) < sizeof(outmsg) - 2) {
  2501.                         strcpy(p, buf);
  2502.                         p += strlen(buf);
  2503.                     }
  2504.                     break;
  2505.                 case 't' :
  2506.                 case 'T' :
  2507.                     CTFSay_Team_Tech(who, buf);
  2508.                     if (strlen(buf) + (p - outmsg) < sizeof(outmsg) - 2) {
  2509.                         strcpy(p, buf);
  2510.                         p += strlen(buf);
  2511.                     }
  2512.                     break;
  2513.                 case 'w' :
  2514.                 case 'W' :
  2515.                     CTFSay_Team_Weapon(who, buf);
  2516.                     if (strlen(buf) + (p - outmsg) < sizeof(outmsg) - 2) {
  2517.                         strcpy(p, buf);
  2518.                         p += strlen(buf);
  2519.                     }
  2520.                     break;
  2521.  
  2522.                 case 'n' :
  2523.                 case 'N' :
  2524.                     CTFSay_Team_Sight(who, buf);
  2525.                     if (strlen(buf) + (p - outmsg) < sizeof(outmsg) - 2) {
  2526.                         strcpy(p, buf);
  2527.                         p += strlen(buf);
  2528.                     }
  2529.                     break;
  2530.  
  2531.                 default :
  2532.                     *p++ = *msg;
  2533.             }
  2534.         } else
  2535.             *p++ = *msg;
  2536.     }
  2537.     *p = 0;
  2538.  
  2539.     for (i = 0; i < maxclients->value; i++) {
  2540.         cl_ent = g_edicts + 1 + i;
  2541.         if (!cl_ent->inuse)
  2542.             continue;
  2543.         if (cl_ent->client->resp.ctf_team == who->client->resp.ctf_team)
  2544.             gi.cprintf(cl_ent, PRINT_CHAT, "(%s): %s\n", 
  2545.                 who->client->pers.netname, outmsg);
  2546.     }
  2547. }
  2548.  
  2549. /*-----------------------------------------------------------------------*/
  2550. /*QUAKED misc_ctf_banner (1 .5 0) (-4 -64 0) (4 64 248) TEAM2
  2551. The origin is the bottom of the banner.
  2552. The banner is 248 tall.
  2553. */
  2554. static void misc_ctf_banner_think (edict_t *ent)
  2555. {
  2556.     ent->s.frame = (ent->s.frame + 1) % 16;
  2557.     ent->nextthink = level.time + FRAMETIME;
  2558. }
  2559.  
  2560. void SP_misc_ctf_banner (edict_t *ent)
  2561. {
  2562.     ent->movetype = MOVETYPE_NONE;
  2563.     ent->solid = SOLID_NOT;
  2564.     ent->s.modelindex = gi.modelindex ("models/ctf/banner/tris.md2");
  2565.     if (ent->spawnflags & 1) // team2
  2566.         ent->s.skinnum = 1;
  2567.  
  2568.     ent->s.frame = rand() % 16;
  2569.     gi.linkentity (ent);
  2570.  
  2571.     ent->think = misc_ctf_banner_think;
  2572.     ent->nextthink = level.time + FRAMETIME;
  2573. }
  2574.  
  2575. /*QUAKED misc_ctf_small_banner (1 .5 0) (-4 -32 0) (4 32 124) TEAM2
  2576. The origin is the bottom of the banner.
  2577. The banner is 124 tall.
  2578. */
  2579. void SP_misc_ctf_small_banner (edict_t *ent)
  2580. {
  2581.     ent->movetype = MOVETYPE_NONE;
  2582.     ent->solid = SOLID_NOT;
  2583.     ent->s.modelindex = gi.modelindex ("models/ctf/banner/small.md2");
  2584.     if (ent->spawnflags & 1) // team2
  2585.         ent->s.skinnum = 1;
  2586.  
  2587.     ent->s.frame = rand() % 16;
  2588.     gi.linkentity (ent);
  2589.  
  2590.     ent->think = misc_ctf_banner_think;
  2591.     ent->nextthink = level.time + FRAMETIME;
  2592. }
  2593.  
  2594. /*-----------------------------------------------------------------------*/
  2595.  
  2596. static void SetLevelName(pmenu_t *p)
  2597. {
  2598.     static char levelname[33];
  2599.  
  2600.     levelname[0] = '*';
  2601.     if (g_edicts[0].message)
  2602.         strncpy(levelname+1, g_edicts[0].message, sizeof(levelname) - 2);
  2603.     else
  2604.         strncpy(levelname+1, level.mapname, sizeof(levelname) - 2);
  2605.     levelname[sizeof(levelname) - 1] = 0;
  2606.     p->text = levelname;
  2607. }
  2608.  
  2609.  
  2610. /*-----------------------------------------------------------------------*/
  2611.  
  2612.  
  2613. /* ELECTIONS */
  2614.  
  2615. qboolean CTFBeginElection(edict_t *ent, elect_t type, char *msg)
  2616. {
  2617.     int i;
  2618.     int count;
  2619.     edict_t *e;
  2620.  
  2621.     if (electpercentage->value == 0) {
  2622.         gi.cprintf(ent, PRINT_HIGH, "Elections are disabled, only an admin can process this action.\n");
  2623.         return false;
  2624.     }
  2625.  
  2626.  
  2627.     if (ctfgame.election != ELECT_NONE) {
  2628.         gi.cprintf(ent, PRINT_HIGH, "Election already in progress.\n");
  2629.         return false;
  2630.     }
  2631.  
  2632.     // clear votes
  2633.     count = 0;
  2634.     for (i = 1; i <= maxclients->value; i++) {
  2635.         e = g_edicts + i;
  2636.         e->client->resp.voted = false;
  2637.         if (e->inuse)
  2638.             count++;
  2639.     }
  2640.  
  2641.     if (count < 2) {
  2642.         gi.cprintf(ent, PRINT_HIGH, "Not enough players for election.\n");
  2643.         return false;
  2644.     }
  2645.  
  2646.     ctfgame.etarget = ent;
  2647.     ctfgame.election = type;
  2648.     ctfgame.evotes = 0;
  2649.     ctfgame.needvotes = (count * electpercentage->value) / 100;
  2650.     ctfgame.electtime = level.time + 20; // twenty seconds for election
  2651.     strncpy(ctfgame.emsg, msg, sizeof(ctfgame.emsg) - 1);
  2652.  
  2653.     // tell everyone
  2654.     gi.bprintf(PRINT_CHAT, "%s\n", ctfgame.emsg);
  2655.     gi.bprintf(PRINT_HIGH, "Type YES or NO to vote on this request.\n");
  2656.     gi.bprintf(PRINT_HIGH, "Votes: %d  Needed: %d  Time left: %ds\n", ctfgame.evotes, ctfgame.needvotes,
  2657.         (int)(ctfgame.electtime - level.time));
  2658.  
  2659.     return true;
  2660. }
  2661.  
  2662. void DoRespawn (edict_t *ent);
  2663.  
  2664. void CTFResetAllPlayers(void)
  2665. {
  2666.     int i;
  2667.     edict_t *ent;
  2668.  
  2669.     for (i = 1; i <= maxclients->value; i++) {
  2670.         ent = g_edicts + i;
  2671.         if (!ent->inuse)
  2672.             continue;
  2673.  
  2674.         if (ent->client->menu)
  2675.             PMenu_Close(ent);
  2676.  
  2677.         CTFPlayerResetGrapple(ent);
  2678.         CTFDeadDropFlag(ent);
  2679.         CTFDeadDropTech(ent);
  2680.  
  2681.         ent->client->resp.ctf_team = CTF_NOTEAM;
  2682.         ent->client->resp.ready = false;
  2683.  
  2684.         ent->svflags = 0;
  2685.         ent->flags &= ~FL_GODMODE;
  2686.         PutClientInServer(ent);
  2687.     }
  2688.  
  2689.     // reset the level
  2690.     CTFResetTech();
  2691.     CTFResetFlags();
  2692.  
  2693.     for (ent = g_edicts + 1, i = 1; i < globals.num_edicts; i++, ent++) {
  2694.         if (ent->inuse && !ent->client) {
  2695.             if (ent->solid == SOLID_NOT && ent->think == DoRespawn &&
  2696.                 ent->nextthink >= level.time) {
  2697.                 ent->nextthink = 0;
  2698.                 DoRespawn(ent);
  2699.             }
  2700.         }
  2701.     }
  2702.     if (ctfgame.match == MATCH_SETUP)
  2703.         ctfgame.matchtime = level.time + matchsetuptime->value * 60;
  2704. }
  2705.  
  2706. void CTFAssignGhost(edict_t *ent)
  2707. {
  2708.     int ghost, i;
  2709.  
  2710.     for (ghost = 0; ghost < MAX_CLIENTS; ghost++)
  2711.         if (!ctfgame.ghosts[ghost].code)
  2712.             break;
  2713.     if (ghost == MAX_CLIENTS)
  2714.         return;
  2715.     ctfgame.ghosts[ghost].team = ent->client->resp.ctf_team;
  2716.     ctfgame.ghosts[ghost].score = 0;
  2717.     for (;;) {
  2718.         ctfgame.ghosts[ghost].code = 10000 + (rand() % 90000);
  2719.         for (i = 0; i < MAX_CLIENTS; i++)
  2720.             if (i != ghost && ctfgame.ghosts[i].code == ctfgame.ghosts[ghost].code)
  2721.                 break;
  2722.         if (i == MAX_CLIENTS)
  2723.             break;
  2724.     }
  2725.     ctfgame.ghosts[ghost].ent = ent;
  2726.     strcpy(ctfgame.ghosts[ghost].netname, ent->client->pers.netname);
  2727.     ent->client->resp.ghost = ctfgame.ghosts + ghost;
  2728.     gi.cprintf(ent, PRINT_CHAT, "Your ghost code is **** %d ****\n", ctfgame.ghosts[ghost].code);
  2729.     gi.cprintf(ent, PRINT_HIGH, "If you lose connection, you can rejoin with your score "
  2730.         "intact by typing \"ghost %d\".\n", ctfgame.ghosts[ghost].code);
  2731. }
  2732.  
  2733. // start a match
  2734. void CTFStartMatch(void)
  2735. {
  2736.     int i;
  2737.     edict_t *ent;
  2738.     int ghost = 0;
  2739.  
  2740.     ctfgame.match = MATCH_GAME;
  2741.     ctfgame.matchtime = level.time + matchtime->value * 60;
  2742.     ctfgame.countdown = false;
  2743.  
  2744.     ctfgame.team1 = ctfgame.team2 = 0;
  2745.  
  2746.     memset(ctfgame.ghosts, 0, sizeof(ctfgame.ghosts));
  2747.  
  2748.     for (i = 1; i <= maxclients->value; i++) {
  2749.         ent = g_edicts + i;
  2750.         if (!ent->inuse)
  2751.             continue;
  2752.  
  2753.         ent->client->resp.score = 0;
  2754.         ent->client->resp.ctf_state = 0;
  2755.         ent->client->resp.ghost = NULL;
  2756.  
  2757.         gi.centerprintf(ent, "******************\n\nMATCH HAS STARTED!\n\n******************");
  2758.  
  2759.         if (ent->client->resp.ctf_team != CTF_NOTEAM) {
  2760.             // make up a ghost code
  2761.             CTFAssignGhost(ent);
  2762.             CTFPlayerResetGrapple(ent);
  2763.             ent->svflags = SVF_NOCLIENT;
  2764.             ent->flags &= ~FL_GODMODE;
  2765.  
  2766.             ent->client->respawn_time = level.time + 1.0 + ((rand()%30)/10.0);
  2767.             ent->client->ps.pmove.pm_type = PM_DEAD;
  2768.             ent->client->anim_priority = ANIM_DEATH;
  2769.             ent->s.frame = FRAME_death308-1;
  2770.             ent->client->anim_end = FRAME_death308;
  2771.             ent->deadflag = DEAD_DEAD;
  2772.             ent->movetype = MOVETYPE_NOCLIP;
  2773.             ent->client->ps.gunindex = 0;
  2774.             gi.linkentity (ent);
  2775.         }
  2776.     }
  2777. }
  2778.  
  2779. void CTFEndMatch(void)
  2780. {
  2781.     ctfgame.match = MATCH_POST;
  2782.     gi.bprintf(PRINT_CHAT, "MATCH COMPLETED!\n");
  2783.  
  2784.     CTFCalcScores();
  2785.  
  2786.     gi.bprintf(PRINT_HIGH, "RED TEAM:  %d captures, %d points\n",
  2787.         ctfgame.team1, ctfgame.total1);
  2788.     gi.bprintf(PRINT_HIGH, "BLUE TEAM:  %d captures, %d points\n",
  2789.         ctfgame.team2, ctfgame.total2);
  2790.  
  2791.     if (ctfgame.team1 > ctfgame.team2)
  2792.         gi.bprintf(PRINT_CHAT, "RED team won over the BLUE team by %d CAPTURES!\n",
  2793.             ctfgame.team1 - ctfgame.team2);
  2794.     else if (ctfgame.team2 > ctfgame.team1)
  2795.         gi.bprintf(PRINT_CHAT, "BLUE team won over the RED team by %d CAPTURES!\n",
  2796.             ctfgame.team2 - ctfgame.team1);
  2797.     else if (ctfgame.total1 > ctfgame.total2) // frag tie breaker
  2798.         gi.bprintf(PRINT_CHAT, "RED team won over the BLUE team by %d POINTS!\n",
  2799.             ctfgame.total1 - ctfgame.total2);
  2800.     else if (ctfgame.total2 > ctfgame.total1) 
  2801.         gi.bprintf(PRINT_CHAT, "BLUE team won over the RED team by %d POINTS!\n",
  2802.             ctfgame.total2 - ctfgame.total1);
  2803.     else
  2804.         gi.bprintf(PRINT_CHAT, "TIE GAME!\n");
  2805.  
  2806.     EndDMLevel();
  2807. }
  2808.  
  2809. qboolean CTFNextMap(void)
  2810. {
  2811.     if (ctfgame.match == MATCH_POST) {
  2812.         ctfgame.match = MATCH_SETUP;
  2813.         CTFResetAllPlayers();
  2814.         return true;
  2815.     }
  2816.     return false;
  2817. }
  2818.  
  2819. void CTFWinElection(void)
  2820. {
  2821.     switch (ctfgame.election) {
  2822.     case ELECT_MATCH :
  2823.         // reset into match mode
  2824.         if (competition->value < 3)
  2825.             gi.cvar_set("competition", "2");
  2826.         ctfgame.match = MATCH_SETUP;
  2827.         CTFResetAllPlayers();
  2828.         break;
  2829.  
  2830.     case ELECT_ADMIN :
  2831.         ctfgame.etarget->client->resp.admin = true;
  2832.         gi.bprintf(PRINT_HIGH, "%s has become an admin.\n", ctfgame.etarget->client->pers.netname);
  2833.         gi.cprintf(ctfgame.etarget, PRINT_HIGH, "Type 'admin' to access the adminstration menu.\n");
  2834.         break;
  2835.  
  2836.     case ELECT_MAP :
  2837.         gi.bprintf(PRINT_HIGH, "%s is warping to level %s.\n", 
  2838.             ctfgame.etarget->client->pers.netname, ctfgame.elevel);
  2839.         strncpy(level.forcemap, ctfgame.elevel, sizeof(level.forcemap) - 1);
  2840.         EndDMLevel();
  2841.         break;
  2842.     }
  2843.     ctfgame.election = ELECT_NONE;
  2844. }
  2845.  
  2846. void CTFVoteYes(edict_t *ent)
  2847. {
  2848.     if (ctfgame.election == ELECT_NONE) {
  2849.         gi.cprintf(ent, PRINT_HIGH, "No election is in progress.\n");
  2850.         return;
  2851.     }
  2852.     if (ent->client->resp.voted) {
  2853.         gi.cprintf(ent, PRINT_HIGH, "You already voted.\n");
  2854.         return;
  2855.     }
  2856.     if (ctfgame.etarget == ent) {
  2857.         gi.cprintf(ent, PRINT_HIGH, "You can't vote for yourself.\n");
  2858.         return;
  2859.     }
  2860.  
  2861.     ent->client->resp.voted = true;
  2862.  
  2863.     ctfgame.evotes++;
  2864.     if (ctfgame.evotes == ctfgame.needvotes) {
  2865.         // the election has been won
  2866.         CTFWinElection();
  2867.         return;
  2868.     }
  2869.     gi.bprintf(PRINT_HIGH, "%s\n", ctfgame.emsg);
  2870.     gi.bprintf(PRINT_CHAT, "Votes: %d  Needed: %d  Time left: %ds\n", ctfgame.evotes, ctfgame.needvotes,
  2871.         (int)(ctfgame.electtime - level.time));
  2872. }
  2873.  
  2874. void CTFVoteNo(edict_t *ent)
  2875. {
  2876.     if (ctfgame.election == ELECT_NONE) {
  2877.         gi.cprintf(ent, PRINT_HIGH, "No election is in progress.\n");
  2878.         return;
  2879.     }
  2880.     if (ent->client->resp.voted) {
  2881.         gi.cprintf(ent, PRINT_HIGH, "You already voted.\n");
  2882.         return;
  2883.     }
  2884.     if (ctfgame.etarget == ent) {
  2885.         gi.cprintf(ent, PRINT_HIGH, "You can't vote for yourself.\n");
  2886.         return;
  2887.     }
  2888.  
  2889.     ent->client->resp.voted = true;
  2890.  
  2891.     gi.bprintf(PRINT_HIGH, "%s\n", ctfgame.emsg);
  2892.     gi.bprintf(PRINT_CHAT, "Votes: %d  Needed: %d  Time left: %ds\n", ctfgame.evotes, ctfgame.needvotes,
  2893.         (int)(ctfgame.electtime - level.time));
  2894. }
  2895.  
  2896. void CTFReady(edict_t *ent)
  2897. {
  2898.     int i, j;
  2899.     edict_t *e;
  2900.     int t1, t2;
  2901.  
  2902.     if (ent->client->resp.ctf_team == CTF_NOTEAM) {
  2903.         gi.cprintf(ent, PRINT_HIGH, "Pick a team first (hit <TAB> for menu)\n");
  2904.         return;
  2905.     }
  2906.  
  2907.     if (ctfgame.match != MATCH_SETUP) {
  2908.         gi.cprintf(ent, PRINT_HIGH, "A match is not being setup.\n");
  2909.         return;
  2910.     }
  2911.  
  2912.     if (ent->client->resp.ready) {
  2913.         gi.cprintf(ent, PRINT_HIGH, "You have already commited.\n");
  2914.         return;
  2915.     }
  2916.  
  2917.     ent->client->resp.ready = true;
  2918.     gi.bprintf(PRINT_HIGH, "%s is ready.\n", ent->client->pers.netname);
  2919.  
  2920.     t1 = t2 = 0;
  2921.     for (j = 0, i = 1; i <= maxclients->value; i++) {
  2922.         e = g_edicts + i;
  2923.         if (!e->inuse)
  2924.             continue;
  2925.         if (e->client->resp.ctf_team != CTF_NOTEAM && !e->client->resp.ready)
  2926.             j++;
  2927.         if (e->client->resp.ctf_team == CTF_TEAM1)
  2928.             t1++;
  2929.         else if (e->client->resp.ctf_team == CTF_TEAM2)
  2930.             t2++;
  2931.     }
  2932.     if (!j && t1 && t2) {
  2933.         // everyone has commited
  2934.         gi.bprintf(PRINT_CHAT, "All players have commited.  Match starting\n");
  2935.         ctfgame.match = MATCH_PREGAME;
  2936.         ctfgame.matchtime = level.time + matchstarttime->value;
  2937.         ctfgame.countdown = false;
  2938.         gi.positioned_sound (world->s.origin, world, CHAN_AUTO | CHAN_RELIABLE, gi.soundindex("misc/talk1.wav"), 1, ATTN_NONE, 0);
  2939.     }
  2940. }
  2941.  
  2942. void CTFNotReady(edict_t *ent)
  2943. {
  2944.     if (ent->client->resp.ctf_team == CTF_NOTEAM) {
  2945.         gi.cprintf(ent, PRINT_HIGH, "Pick a team first (hit <TAB> for menu)\n");
  2946.         return;
  2947.     }
  2948.  
  2949.     if (ctfgame.match != MATCH_SETUP && ctfgame.match != MATCH_PREGAME) {
  2950.         gi.cprintf(ent, PRINT_HIGH, "A match is not being setup.\n");
  2951.         return;
  2952.     }
  2953.  
  2954.     if (!ent->client->resp.ready) {
  2955.         gi.cprintf(ent, PRINT_HIGH, "You haven't commited.\n");
  2956.         return;
  2957.     }
  2958.  
  2959.     ent->client->resp.ready = false;
  2960.     gi.bprintf(PRINT_HIGH, "%s is no longer ready.\n", ent->client->pers.netname);
  2961.  
  2962.     if (ctfgame.match == MATCH_PREGAME) {
  2963.         gi.bprintf(PRINT_CHAT, "Match halted.\n");
  2964.         ctfgame.match = MATCH_SETUP;
  2965.         ctfgame.matchtime = level.time + matchsetuptime->value * 60;
  2966.     }
  2967. }
  2968.  
  2969. void CTFGhost(edict_t *ent)
  2970. {
  2971.     int i;
  2972.     int n;
  2973.  
  2974.     if (gi.argc() < 2) {
  2975.         gi.cprintf(ent, PRINT_HIGH, "Usage:  ghost <code>\n");
  2976.         return;
  2977.     }
  2978.  
  2979.     if (ent->client->resp.ctf_team != CTF_NOTEAM) {
  2980.         gi.cprintf(ent, PRINT_HIGH, "You are already in the game.\n");
  2981.         return;
  2982.     }
  2983.     if (ctfgame.match != MATCH_GAME) {
  2984.         gi.cprintf(ent, PRINT_HIGH, "No match is in progress.\n");
  2985.         return;
  2986.     }
  2987.  
  2988.     n = atoi(gi.argv(1));
  2989.  
  2990.     for (i = 0; i < MAX_CLIENTS; i++) {
  2991.         if (ctfgame.ghosts[i].code && ctfgame.ghosts[i].code == n) {
  2992.             gi.cprintf(ent, PRINT_HIGH, "Ghost code accepted, your position has been reinstated.\n");
  2993.             ctfgame.ghosts[i].ent->client->resp.ghost = NULL;
  2994.             ent->client->resp.ctf_team = ctfgame.ghosts[i].team;
  2995.             ent->client->resp.ghost = ctfgame.ghosts + i;
  2996.             ent->client->resp.score = ctfgame.ghosts[i].score;
  2997.             ent->client->resp.ctf_state = 0;
  2998.             ctfgame.ghosts[i].ent = ent;
  2999.             ent->svflags = 0;
  3000.             ent->flags &= ~FL_GODMODE;
  3001.             PutClientInServer(ent);
  3002.             gi.bprintf(PRINT_HIGH, "%s has been reinstated to %s team.\n",
  3003.                 ent->client->pers.netname, CTFTeamName(ent->client->resp.ctf_team));
  3004.             return;
  3005.         }
  3006.     }
  3007.     gi.cprintf(ent, PRINT_HIGH, "Invalid ghost code.\n");
  3008. }
  3009.  
  3010. qboolean CTFMatchSetup(void)
  3011. {
  3012.     if (ctfgame.match == MATCH_SETUP || ctfgame.match == MATCH_PREGAME)
  3013.         return true;
  3014.     return false;
  3015. }
  3016.  
  3017. qboolean CTFMatchOn(void)
  3018. {
  3019.     if (ctfgame.match == MATCH_GAME)
  3020.         return true;
  3021.     return false;
  3022. }
  3023.  
  3024.  
  3025. /*-----------------------------------------------------------------------*/
  3026.  
  3027. void CTFJoinTeam1(edict_t *ent, pmenuhnd_t *p);
  3028. void CTFJoinTeam2(edict_t *ent, pmenuhnd_t *p);
  3029. void CTFCredits(edict_t *ent, pmenuhnd_t *p);
  3030. void CTFReturnToMain(edict_t *ent, pmenuhnd_t *p);
  3031. void CTFChaseCam(edict_t *ent, pmenuhnd_t *p);
  3032.  
  3033. pmenu_t creditsmenu[] = {
  3034.     { "*Quake II",                        PMENU_ALIGN_CENTER, NULL },
  3035.     { "*ThreeWave Capture the Flag",    PMENU_ALIGN_CENTER, NULL },
  3036.     { NULL,                                PMENU_ALIGN_CENTER, NULL },
  3037.     { "*Programming",                    PMENU_ALIGN_CENTER, NULL }, 
  3038.     { "Dave 'Zoid' Kirsch",                PMENU_ALIGN_CENTER, NULL },
  3039.     { "*Level Design",                     PMENU_ALIGN_CENTER, NULL },
  3040.     { "Christian Antkow",                PMENU_ALIGN_CENTER, NULL },
  3041.     { "Tim Willits",                    PMENU_ALIGN_CENTER, NULL },
  3042.     { "Dave 'Zoid' Kirsch",                PMENU_ALIGN_CENTER, NULL },
  3043.     { "*Art",                            PMENU_ALIGN_CENTER, NULL },
  3044.     { "Adrian Carmack Paul Steed",        PMENU_ALIGN_CENTER, NULL },
  3045.     { "Kevin Cloud",                    PMENU_ALIGN_CENTER, NULL },
  3046.     { "*Sound",                            PMENU_ALIGN_CENTER, NULL },
  3047.     { "Tom 'Bjorn' Klok",                PMENU_ALIGN_CENTER, NULL },
  3048.     { "*Original CTF Art Design",        PMENU_ALIGN_CENTER, NULL },
  3049.     { "Brian 'Whaleboy' Cozzens",        PMENU_ALIGN_CENTER, NULL },
  3050.     { NULL,                                PMENU_ALIGN_CENTER, NULL },
  3051.     { "Return to Main Menu",            PMENU_ALIGN_LEFT, CTFReturnToMain }
  3052. };
  3053.  
  3054. static const int jmenu_level = 2;
  3055. static const int jmenu_match = 3;
  3056. static const int jmenu_red = 5;
  3057. static const int jmenu_blue = 7;
  3058. static const int jmenu_chase = 9;
  3059. static const int jmenu_reqmatch = 11;
  3060.  
  3061. pmenu_t joinmenu[] = {
  3062.     { "*Quake II",            PMENU_ALIGN_CENTER, NULL },
  3063.     { "*ThreeWave Capture the Flag",    PMENU_ALIGN_CENTER, NULL },
  3064.     { NULL,                    PMENU_ALIGN_CENTER, NULL },
  3065.     { NULL,                    PMENU_ALIGN_CENTER, NULL },
  3066.     { NULL,                    PMENU_ALIGN_CENTER, NULL },
  3067.     { "Join Red Team",        PMENU_ALIGN_LEFT, CTFJoinTeam1 },
  3068.     { NULL,                    PMENU_ALIGN_LEFT, NULL },
  3069.     { "Join Blue Team",        PMENU_ALIGN_LEFT, CTFJoinTeam2 },
  3070.     { NULL,                    PMENU_ALIGN_LEFT, NULL },
  3071.     { "Chase Camera",        PMENU_ALIGN_LEFT, CTFChaseCam },
  3072.     { "Credits",            PMENU_ALIGN_LEFT, CTFCredits },
  3073.     { NULL,                    PMENU_ALIGN_LEFT, NULL },
  3074.     { NULL,                    PMENU_ALIGN_LEFT, NULL },
  3075.     { "Use [ and ] to move cursor",    PMENU_ALIGN_LEFT, NULL },
  3076.     { "ENTER to select",    PMENU_ALIGN_LEFT, NULL },
  3077.     { "ESC to Exit Menu",    PMENU_ALIGN_LEFT, NULL },
  3078.     { "(TAB to Return)",    PMENU_ALIGN_LEFT, NULL },
  3079.     { "v" CTF_STRING_VERSION,    PMENU_ALIGN_RIGHT, NULL },
  3080. };
  3081.  
  3082. pmenu_t nochasemenu[] = {
  3083.     { "*Quake II",            PMENU_ALIGN_CENTER, NULL },
  3084.     { "*ThreeWave Capture the Flag",    PMENU_ALIGN_CENTER, NULL },
  3085.     { NULL,                    PMENU_ALIGN_CENTER, NULL },
  3086.     { NULL,                    PMENU_ALIGN_CENTER, NULL },
  3087.     { "No one to chase",    PMENU_ALIGN_LEFT, NULL },
  3088.     { NULL,                    PMENU_ALIGN_CENTER, NULL },
  3089.     { "Return to Main Menu", PMENU_ALIGN_LEFT, CTFReturnToMain }
  3090. };
  3091.  
  3092. void CTFJoinTeam(edict_t *ent, int desired_team)
  3093. {
  3094.     char *s;
  3095.  
  3096.     PMenu_Close(ent);
  3097.  
  3098.     ent->svflags &= ~SVF_NOCLIENT;
  3099.     ent->client->resp.ctf_team = desired_team;
  3100.     ent->client->resp.ctf_state = 0;
  3101.     s = Info_ValueForKey (ent->client->pers.userinfo, "skin");
  3102.     CTFAssignSkin(ent, s);
  3103.  
  3104.     // assign a ghost if we are in match mode
  3105.     if (ctfgame.match == MATCH_GAME) {
  3106.         if (ent->client->resp.ghost)
  3107.             ent->client->resp.ghost->code = 0;
  3108.         ent->client->resp.ghost = NULL;
  3109.         CTFAssignGhost(ent);
  3110.     }
  3111.  
  3112.     PutClientInServer (ent);
  3113.     // add a teleportation effect
  3114.     ent->s.event = EV_PLAYER_TELEPORT;
  3115.     // hold in place briefly
  3116.     ent->client->ps.pmove.pm_flags = PMF_TIME_TELEPORT;
  3117.     ent->client->ps.pmove.pm_time = 14;
  3118.     gi.bprintf(PRINT_HIGH, "%s joined the %s team.\n",
  3119.         ent->client->pers.netname, CTFTeamName(desired_team));
  3120.  
  3121.     if (ctfgame.match == MATCH_SETUP) {
  3122.         gi.centerprintf(ent,    "***********************\n"
  3123.                                 "Type \"ready\" in console\n"
  3124.                                 "to ready up.\n"
  3125.                                 "***********************");
  3126.     }
  3127. }
  3128.  
  3129. void CTFJoinTeam1(edict_t *ent, pmenuhnd_t *p)
  3130. {
  3131.     CTFJoinTeam(ent, CTF_TEAM1);
  3132. }
  3133.  
  3134. void CTFJoinTeam2(edict_t *ent, pmenuhnd_t *p)
  3135. {
  3136.     CTFJoinTeam(ent, CTF_TEAM2);
  3137. }
  3138.  
  3139. void CTFChaseCam(edict_t *ent, pmenuhnd_t *p)
  3140. {
  3141.     int i;
  3142.     edict_t *e;
  3143.  
  3144.     if (ent->client->chase_target) {
  3145.         ent->client->chase_target = NULL;
  3146.         ent->client->ps.pmove.pm_flags &= ~PMF_NO_PREDICTION;
  3147.         PMenu_Close(ent);
  3148.         return;
  3149.     }
  3150.  
  3151.     for (i = 1; i <= maxclients->value; i++) {
  3152.         e = g_edicts + i;
  3153.         if (e->inuse && e->solid != SOLID_NOT) {
  3154.             ent->client->chase_target = e;
  3155.             PMenu_Close(ent);
  3156.             ent->client->update_chase = true;
  3157.             return;
  3158.         }
  3159.     }
  3160.  
  3161.     SetLevelName(nochasemenu + jmenu_level);
  3162.  
  3163.     PMenu_Close(ent);
  3164.     PMenu_Open(ent, nochasemenu, -1, sizeof(nochasemenu) / sizeof(pmenu_t), NULL);
  3165. }
  3166.  
  3167. void CTFReturnToMain(edict_t *ent, pmenuhnd_t *p)
  3168. {
  3169.     PMenu_Close(ent);
  3170.     CTFOpenJoinMenu(ent);
  3171. }
  3172.  
  3173. void CTFRequestMatch(edict_t *ent, pmenuhnd_t *p)
  3174. {
  3175.     char text[1024];
  3176.  
  3177.     PMenu_Close(ent);
  3178.  
  3179.     sprintf(text, "%s has requested to switch to competition mode.",
  3180.         ent->client->pers.netname);
  3181.     CTFBeginElection(ent, ELECT_MATCH, text);
  3182. }
  3183.  
  3184. void DeathmatchScoreboard (edict_t *ent);
  3185.  
  3186. void CTFShowScores(edict_t *ent, pmenu_t *p)
  3187. {
  3188.     PMenu_Close(ent);
  3189.  
  3190.     ent->client->showscores = true;
  3191.     ent->client->showinventory = false;
  3192.     DeathmatchScoreboard (ent);
  3193. }
  3194.  
  3195. int CTFUpdateJoinMenu(edict_t *ent)
  3196. {
  3197.     static char team1players[32];
  3198.     static char team2players[32];
  3199.     int num1, num2, i;
  3200.  
  3201.     if (ctfgame.match >= MATCH_PREGAME && matchlock->value) {
  3202.         joinmenu[jmenu_red].text = "MATCH IS LOCKED";
  3203.         joinmenu[jmenu_red].SelectFunc = NULL;
  3204.         joinmenu[jmenu_blue].text = "  (entry is not permitted)";
  3205.         joinmenu[jmenu_blue].SelectFunc = NULL;
  3206.     } else {
  3207.         if (ctfgame.match >= MATCH_PREGAME) {
  3208.             joinmenu[jmenu_red].text = "Join Red MATCH Team";
  3209.             joinmenu[jmenu_blue].text = "Join Blue MATCH Team";
  3210.         } else {
  3211.             joinmenu[jmenu_red].text = "Join Red Team";
  3212.             joinmenu[jmenu_blue].text = "Join Blue Team";
  3213.         }
  3214.         joinmenu[jmenu_red].SelectFunc = CTFJoinTeam1;
  3215.         joinmenu[jmenu_blue].SelectFunc = CTFJoinTeam2;
  3216.     }
  3217.  
  3218.     if (ctf_forcejoin->string && *ctf_forcejoin->string) {
  3219.         if (stricmp(ctf_forcejoin->string, "red") == 0) {
  3220.             joinmenu[jmenu_blue].text = NULL;
  3221.             joinmenu[jmenu_blue].SelectFunc = NULL;
  3222.         } else if (stricmp(ctf_forcejoin->string, "blue") == 0) {
  3223.             joinmenu[jmenu_red].text = NULL;
  3224.             joinmenu[jmenu_red].SelectFunc = NULL;
  3225.         }
  3226.     }
  3227.  
  3228.     if (ent->client->chase_target)
  3229.         joinmenu[jmenu_chase].text = "Leave Chase Camera";
  3230.     else
  3231.         joinmenu[jmenu_chase].text = "Chase Camera";
  3232.  
  3233.     SetLevelName(joinmenu + jmenu_level);
  3234.  
  3235.     num1 = num2 = 0;
  3236.     for (i = 0; i < maxclients->value; i++) {
  3237.         if (!g_edicts[i+1].inuse)
  3238.             continue;
  3239.         if (game.clients[i].resp.ctf_team == CTF_TEAM1)
  3240.             num1++;
  3241.         else if (game.clients[i].resp.ctf_team == CTF_TEAM2)
  3242.             num2++;
  3243.     }
  3244.  
  3245.     sprintf(team1players, "  (%d players)", num1);
  3246.     sprintf(team2players, "  (%d players)", num2);
  3247.  
  3248.     switch (ctfgame.match) {
  3249.     case MATCH_NONE :
  3250.         joinmenu[jmenu_match].text = NULL;
  3251.         break;
  3252.  
  3253.     case MATCH_SETUP :
  3254.         joinmenu[jmenu_match].text = "*MATCH SETUP IN PROGRESS";
  3255.         break;
  3256.  
  3257.     case MATCH_PREGAME :
  3258.         joinmenu[jmenu_match].text = "*MATCH STARTING";
  3259.         break;
  3260.  
  3261.     case MATCH_GAME :
  3262.         joinmenu[jmenu_match].text = "*MATCH IN PROGRESS";
  3263.         break;
  3264.     }
  3265.  
  3266.     if (joinmenu[jmenu_red].text)
  3267.         joinmenu[jmenu_red+1].text = team1players;
  3268.     else
  3269.         joinmenu[jmenu_red+1].text = NULL;
  3270.     if (joinmenu[jmenu_blue].text)
  3271.         joinmenu[jmenu_blue+1].text = team2players;
  3272.     else
  3273.         joinmenu[jmenu_blue+1].text = NULL;
  3274.  
  3275.     joinmenu[jmenu_reqmatch].text = NULL;
  3276.     joinmenu[jmenu_reqmatch].SelectFunc = NULL;
  3277.     if (competition->value && ctfgame.match < MATCH_SETUP) {
  3278.         joinmenu[jmenu_reqmatch].text = "Request Match";
  3279.         joinmenu[jmenu_reqmatch].SelectFunc = CTFRequestMatch;
  3280.     }
  3281.     
  3282.     if (num1 > num2)
  3283.         return CTF_TEAM1;
  3284.     else if (num2 > num1)
  3285.         return CTF_TEAM2;
  3286.     return (rand() & 1) ? CTF_TEAM1 : CTF_TEAM2;
  3287. }
  3288.  
  3289. void CTFOpenJoinMenu(edict_t *ent)
  3290. {
  3291.     int team;
  3292.  
  3293.     team = CTFUpdateJoinMenu(ent);
  3294.     if (ent->client->chase_target)
  3295.         team = 8;
  3296.     else if (team == CTF_TEAM1)
  3297.         team = 4;
  3298.     else
  3299.         team = 6;
  3300.     PMenu_Open(ent, joinmenu, team, sizeof(joinmenu) / sizeof(pmenu_t), NULL);
  3301. }
  3302.  
  3303. void CTFCredits(edict_t *ent, pmenuhnd_t *p)
  3304. {
  3305.     PMenu_Close(ent);
  3306.     PMenu_Open(ent, creditsmenu, -1, sizeof(creditsmenu) / sizeof(pmenu_t), NULL);
  3307. }
  3308.  
  3309. qboolean CTFStartClient(edict_t *ent)
  3310. {
  3311.     if (ent->client->resp.ctf_team != CTF_NOTEAM)
  3312.         return false;
  3313.  
  3314.     if (!((int)dmflags->value & DF_CTF_FORCEJOIN) || ctfgame.match >= MATCH_SETUP) {
  3315.         // start as 'observer'
  3316.         ent->movetype = MOVETYPE_NOCLIP;
  3317.         ent->solid = SOLID_NOT;
  3318.         ent->svflags |= SVF_NOCLIENT;
  3319.         ent->client->resp.ctf_team = CTF_NOTEAM;
  3320.         ent->client->ps.gunindex = 0;
  3321.         gi.linkentity (ent);
  3322.  
  3323.         CTFOpenJoinMenu(ent);
  3324.         return true;
  3325.     }
  3326.     return false;
  3327. }
  3328.  
  3329. void CTFObserver(edict_t *ent)
  3330. {
  3331.     char        userinfo[MAX_INFO_STRING];
  3332.  
  3333.     // start as 'observer'
  3334.     if (ent->movetype == MOVETYPE_NOCLIP)
  3335.  
  3336.     CTFPlayerResetGrapple(ent);
  3337.     CTFDeadDropFlag(ent);
  3338.     CTFDeadDropTech(ent);
  3339.  
  3340.     ent->deadflag = DEAD_NO;
  3341.     ent->movetype = MOVETYPE_NOCLIP;
  3342.     ent->solid = SOLID_NOT;
  3343.     ent->svflags |= SVF_NOCLIENT;
  3344.     ent->client->resp.ctf_team = CTF_NOTEAM;
  3345.     ent->client->ps.gunindex = 0;
  3346.     ent->client->resp.score = 0;
  3347.     memcpy (userinfo, ent->client->pers.userinfo, sizeof(userinfo));
  3348.     InitClientPersistant(ent->client);
  3349.     ClientUserinfoChanged (ent, userinfo);
  3350.     gi.linkentity (ent);
  3351.     CTFOpenJoinMenu(ent);
  3352. }
  3353.  
  3354. qboolean CTFInMatch(void)
  3355. {
  3356.     if (ctfgame.match > MATCH_NONE)
  3357.         return true;
  3358.     return false;
  3359. }
  3360.  
  3361. qboolean CTFCheckRules(void)
  3362. {
  3363.     int t;
  3364.     int i, j;
  3365.     char text[64];
  3366.     edict_t *ent;
  3367.  
  3368.     if (ctfgame.election != ELECT_NONE && ctfgame.electtime <= level.time) {
  3369.         gi.bprintf(PRINT_CHAT, "Election timed out and has been cancelled.\n");
  3370.         ctfgame.election = ELECT_NONE;
  3371.     }
  3372.  
  3373.     if (ctfgame.match != MATCH_NONE) {
  3374.         t = ctfgame.matchtime - level.time;
  3375.  
  3376.         // no team warnings in match mode
  3377.         ctfgame.warnactive = 0;
  3378.  
  3379.         if (t <= 0) { // time ended on something
  3380.             switch (ctfgame.match) {
  3381.             case MATCH_SETUP :
  3382.                 // go back to normal mode
  3383.                 if (competition->value < 3) {
  3384.                     ctfgame.match = MATCH_NONE;
  3385.                     gi.cvar_set("competition", "1");
  3386.                     CTFResetAllPlayers();
  3387.                 } else {
  3388.                     // reset the time
  3389.                     ctfgame.matchtime = level.time + matchsetuptime->value * 60;
  3390.                 }
  3391.                 return false;
  3392.  
  3393.             case MATCH_PREGAME :
  3394.                 // match started!
  3395.                 CTFStartMatch();
  3396.                 gi.positioned_sound (world->s.origin, world, CHAN_AUTO | CHAN_RELIABLE, gi.soundindex("misc/tele_up.wav"), 1, ATTN_NONE, 0);
  3397.                 return false;
  3398.  
  3399.             case MATCH_GAME :
  3400.                 // match ended!
  3401.                 CTFEndMatch();
  3402.                 gi.positioned_sound (world->s.origin, world, CHAN_AUTO | CHAN_RELIABLE, gi.soundindex("misc/bigtele.wav"), 1, ATTN_NONE, 0);
  3403.                 return false;
  3404.             }
  3405.         }
  3406.  
  3407.         if (t == ctfgame.lasttime)
  3408.             return false;
  3409.  
  3410.         ctfgame.lasttime = t;
  3411.  
  3412.         switch (ctfgame.match) {
  3413.         case MATCH_SETUP :
  3414.             for (j = 0, i = 1; i <= maxclients->value; i++) {
  3415.                 ent = g_edicts + i;
  3416.                 if (!ent->inuse)
  3417.                     continue;
  3418.                 if (ent->client->resp.ctf_team != CTF_NOTEAM &&
  3419.                     !ent->client->resp.ready)
  3420.                     j++;
  3421.             }
  3422.  
  3423.             if (competition->value < 3)
  3424.                 sprintf(text, "%02d:%02d SETUP: %d not ready",
  3425.                     t / 60, t % 60, j);
  3426.             else
  3427.                 sprintf(text, "SETUP: %d not ready", j);
  3428.  
  3429.             gi.configstring (CONFIG_CTF_MATCH, text);
  3430.             break;
  3431.  
  3432.  
  3433.         case MATCH_PREGAME :
  3434.             sprintf(text, "%02d:%02d UNTIL START",
  3435.                 t / 60, t % 60);
  3436.             gi.configstring (CONFIG_CTF_MATCH, text);
  3437.  
  3438.             if (t <= 10 && !ctfgame.countdown) {
  3439.                 ctfgame.countdown = true;
  3440.                 gi.positioned_sound (world->s.origin, world, CHAN_AUTO | CHAN_RELIABLE, gi.soundindex("world/10_0.wav"), 1, ATTN_NONE, 0);
  3441.             }
  3442.             break;
  3443.  
  3444.         case MATCH_GAME:
  3445.             sprintf(text, "%02d:%02d MATCH",
  3446.                 t / 60, t % 60);
  3447.             gi.configstring (CONFIG_CTF_MATCH, text);
  3448.             if (t <= 10 && !ctfgame.countdown) {
  3449.                 ctfgame.countdown = true;
  3450.                 gi.positioned_sound (world->s.origin, world, CHAN_AUTO | CHAN_RELIABLE, gi.soundindex("world/10_0.wav"), 1, ATTN_NONE, 0);
  3451.             }
  3452.             break;
  3453.         }
  3454.         return false;
  3455.  
  3456.     } else {
  3457.         int team1 = 0, team2 = 0;
  3458.  
  3459.         if (level.time == ctfgame.lasttime)
  3460.             return false;
  3461.         ctfgame.lasttime = level.time;
  3462.         // this is only done in non-match (public) mode
  3463.  
  3464.         if (warn_unbalanced->value) {
  3465.             // count up the team totals
  3466.             for (i = 1; i <= maxclients->value; i++) {
  3467.                 ent = g_edicts + i;
  3468.                 if (!ent->inuse)
  3469.                     continue;
  3470.                 if (ent->client->resp.ctf_team == CTF_TEAM1)
  3471.                     team1++;
  3472.                 else if (ent->client->resp.ctf_team == CTF_TEAM2)
  3473.                     team2++;
  3474.             }
  3475.  
  3476.             if (team1 - team2 >= 2 && team2 >= 2) {
  3477.                 if (ctfgame.warnactive != CTF_TEAM1) {
  3478.                     ctfgame.warnactive = CTF_TEAM1;
  3479.                     gi.configstring (CONFIG_CTF_TEAMINFO, "WARNING: Red has too many players");
  3480.                 }
  3481.             } else if (team2 - team1 >= 2 && team1 >= 2) {
  3482.                 if (ctfgame.warnactive != CTF_TEAM2) {
  3483.                     ctfgame.warnactive = CTF_TEAM2;
  3484.                     gi.configstring (CONFIG_CTF_TEAMINFO, "WARNING: Blue has too many players");
  3485.                 }
  3486.             } else
  3487.                 ctfgame.warnactive = 0;
  3488.         } else
  3489.             ctfgame.warnactive = 0;
  3490.  
  3491.     }
  3492.  
  3493.  
  3494.  
  3495.     if (capturelimit->value && 
  3496.         (ctfgame.team1 >= capturelimit->value ||
  3497.         ctfgame.team2 >= capturelimit->value)) {
  3498.         gi.bprintf (PRINT_HIGH, "Capturelimit hit.\n");
  3499.         return true;
  3500.     }
  3501.     return false;
  3502. }
  3503.  
  3504. /*--------------------------------------------------------------------------
  3505.  * just here to help old map conversions
  3506.  *--------------------------------------------------------------------------*/
  3507.  
  3508. static void old_teleporter_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf)
  3509. {
  3510.     edict_t        *dest;
  3511.     int            i;
  3512.     vec3_t        forward;
  3513.  
  3514.     if (!other->client)
  3515.         return;
  3516.     dest = G_Find (NULL, FOFS(targetname), self->target);
  3517.     if (!dest)
  3518.     {
  3519.         gi.dprintf ("Couldn't find destination\n");
  3520.         return;
  3521.     }
  3522.  
  3523. //ZOID
  3524.     CTFPlayerResetGrapple(other);
  3525. //ZOID
  3526.  
  3527.     // unlink to make sure it can't possibly interfere with KillBox
  3528.     gi.unlinkentity (other);
  3529.  
  3530.     VectorCopy (dest->s.origin, other->s.origin);
  3531.     VectorCopy (dest->s.origin, other->s.old_origin);
  3532. //    other->s.origin[2] += 10;
  3533.  
  3534.     // clear the velocity and hold them in place briefly
  3535.     VectorClear (other->velocity);
  3536.     other->client->ps.pmove.pm_time = 160>>3;        // hold time
  3537.     other->client->ps.pmove.pm_flags |= PMF_TIME_TELEPORT;
  3538.  
  3539.     // draw the teleport splash at source and on the player
  3540.     self->enemy->s.event = EV_PLAYER_TELEPORT;
  3541.     other->s.event = EV_PLAYER_TELEPORT;
  3542.  
  3543.     // set angles
  3544.     for (i=0 ; i<3 ; i++)
  3545.         other->client->ps.pmove.delta_angles[i] = ANGLE2SHORT(dest->s.angles[i] - other->client->resp.cmd_angles[i]);
  3546.  
  3547.     other->s.angles[PITCH] = 0;
  3548.     other->s.angles[YAW] = dest->s.angles[YAW];
  3549.     other->s.angles[ROLL] = 0;
  3550.     VectorCopy (dest->s.angles, other->client->ps.viewangles);
  3551.     VectorCopy (dest->s.angles, other->client->v_angle);
  3552.  
  3553.     // give a little forward velocity
  3554.     AngleVectors (other->client->v_angle, forward, NULL, NULL);
  3555.     VectorScale(forward, 200, other->velocity);
  3556.  
  3557.     // kill anything at the destination
  3558.     if (!KillBox (other))
  3559.     {
  3560.     }
  3561.  
  3562.     gi.linkentity (other);
  3563. }
  3564.  
  3565. /*QUAKED trigger_teleport (0.5 0.5 0.5) ?
  3566. Players touching this will be teleported
  3567. */
  3568. void SP_trigger_teleport (edict_t *ent)
  3569. {
  3570.     edict_t *s;
  3571.     int i;
  3572.  
  3573.     if (!ent->target)
  3574.     {
  3575.         gi.dprintf ("teleporter without a target.\n");
  3576.         G_FreeEdict (ent);
  3577.         return;
  3578.     }
  3579.  
  3580.     ent->svflags |= SVF_NOCLIENT;
  3581.     ent->solid = SOLID_TRIGGER;
  3582.     ent->touch = old_teleporter_touch;
  3583.     gi.setmodel (ent, ent->model);
  3584.     gi.linkentity (ent);
  3585.  
  3586.     // noise maker and splash effect dude
  3587.     s = G_Spawn();
  3588.     ent->enemy = s;
  3589.     for (i = 0; i < 3; i++)
  3590.         s->s.origin[i] = ent->mins[i] + (ent->maxs[i] - ent->mins[i])/2;
  3591.     s->s.sound = gi.soundindex ("world/hum1.wav");
  3592.     gi.linkentity(s);
  3593.     
  3594. }
  3595.  
  3596. /*QUAKED info_teleport_destination (0.5 0.5 0.5) (-16 -16 -24) (16 16 32)
  3597. Point trigger_teleports at these.
  3598. */
  3599. void SP_info_teleport_destination (edict_t *ent)
  3600. {
  3601.     ent->s.origin[2] += 16;
  3602. }
  3603.  
  3604. /*----------------------------------------------------------------------------------*/
  3605. /* ADMIN */
  3606.  
  3607. typedef struct admin_settings_s {
  3608.     int matchlen;
  3609.     int matchsetuplen;
  3610.     int matchstartlen;
  3611.     qboolean weaponsstay;
  3612.     qboolean instantitems;
  3613.     qboolean quaddrop;
  3614.     qboolean instantweap;
  3615.     qboolean matchlock;
  3616. } admin_settings_t;
  3617.  
  3618. #define SETMENU_SIZE (7 + 5)
  3619.  
  3620. void CTFAdmin_UpdateSettings(edict_t *ent, pmenuhnd_t *setmenu);
  3621. void CTFOpenAdminMenu(edict_t *ent);
  3622.  
  3623. void CTFAdmin_SettingsApply(edict_t *ent, pmenuhnd_t *p)
  3624. {
  3625.     admin_settings_t *settings = p->arg;
  3626.     char st[80];
  3627.     int i;
  3628.  
  3629.     if (settings->matchlen != matchtime->value) {
  3630.         gi.bprintf(PRINT_HIGH, "%s changed the match length to %d minutes.\n",
  3631.             ent->client->pers.netname, settings->matchlen);
  3632.         if (ctfgame.match == MATCH_GAME) {
  3633.             // in the middle of a match, change it on the fly
  3634.             ctfgame.matchtime = (ctfgame.matchtime - matchtime->value*60) + settings->matchlen*60;
  3635.         } 
  3636.         sprintf(st, "%d", settings->matchlen);
  3637.         gi.cvar_set("matchtime", st);
  3638.     }
  3639.  
  3640.     if (settings->matchsetuplen != matchsetuptime->value) {
  3641.         gi.bprintf(PRINT_HIGH, "%s changed the match setup time to %d minutes.\n",
  3642.             ent->client->pers.netname, settings->matchsetuplen);
  3643.         if (ctfgame.match == MATCH_SETUP) {
  3644.             // in the middle of a match, change it on the fly
  3645.             ctfgame.matchtime = (ctfgame.matchtime - matchsetuptime->value*60) + settings->matchsetuplen*60;
  3646.         } 
  3647.         sprintf(st, "%d", settings->matchsetuplen);
  3648.         gi.cvar_set("matchsetuptime", st);
  3649.     }
  3650.  
  3651.     if (settings->matchstartlen != matchstarttime->value) {
  3652.         gi.bprintf(PRINT_HIGH, "%s changed the match start time to %d seconds.\n",
  3653.             ent->client->pers.netname, settings->matchstartlen);
  3654.         if (ctfgame.match == MATCH_PREGAME) {
  3655.             // in the middle of a match, change it on the fly
  3656.             ctfgame.matchtime = (ctfgame.matchtime - matchstarttime->value) + settings->matchstartlen;
  3657.         } 
  3658.         sprintf(st, "%d", settings->matchstartlen);
  3659.         gi.cvar_set("matchstarttime", st);
  3660.     }
  3661.  
  3662.     if (settings->weaponsstay != !!((int)dmflags->value & DF_WEAPONS_STAY)) {
  3663.         gi.bprintf(PRINT_HIGH, "%s turned %s weapons stay.\n",
  3664.             ent->client->pers.netname, settings->weaponsstay ? "on" : "off");
  3665.         i = (int)dmflags->value;
  3666.         if (settings->weaponsstay)
  3667.             i |= DF_WEAPONS_STAY;
  3668.         else
  3669.             i &= ~DF_WEAPONS_STAY;
  3670.         sprintf(st, "%d", i);
  3671.         gi.cvar_set("dmflags", st);
  3672.     }
  3673.  
  3674.     if (settings->instantitems != !!((int)dmflags->value & DF_INSTANT_ITEMS)) {
  3675.         gi.bprintf(PRINT_HIGH, "%s turned %s instant items.\n",
  3676.             ent->client->pers.netname, settings->instantitems ? "on" : "off");
  3677.         i = (int)dmflags->value;
  3678.         if (settings->instantitems)
  3679.             i |= DF_INSTANT_ITEMS;
  3680.         else
  3681.             i &= ~DF_INSTANT_ITEMS;
  3682.         sprintf(st, "%d", i);
  3683.         gi.cvar_set("dmflags", st);
  3684.     }
  3685.  
  3686.     if (settings->quaddrop != !!((int)dmflags->value & DF_QUAD_DROP)) {
  3687.         gi.bprintf(PRINT_HIGH, "%s turned %s quad drop.\n",
  3688.             ent->client->pers.netname, settings->quaddrop ? "on" : "off");
  3689.         i = (int)dmflags->value;
  3690.         if (settings->quaddrop)
  3691.             i |= DF_QUAD_DROP;
  3692.         else
  3693.             i &= ~DF_QUAD_DROP;
  3694.         sprintf(st, "%d", i);
  3695.         gi.cvar_set("dmflags", st);
  3696.     }
  3697.  
  3698.     if (settings->instantweap != !!((int)instantweap->value)) {
  3699.         gi.bprintf(PRINT_HIGH, "%s turned %s instant weapons.\n",
  3700.             ent->client->pers.netname, settings->instantweap ? "on" : "off");
  3701.         sprintf(st, "%d", (int)settings->instantweap);
  3702.         gi.cvar_set("instantweap", st);
  3703.     }
  3704.  
  3705.     if (settings->matchlock != !!((int)matchlock->value)) {
  3706.         gi.bprintf(PRINT_HIGH, "%s turned %s match lock.\n",
  3707.             ent->client->pers.netname, settings->matchlock ? "on" : "off");
  3708.         sprintf(st, "%d", (int)settings->matchlock);
  3709.         gi.cvar_set("matchlock", st);
  3710.     }
  3711.  
  3712.     PMenu_Close(ent);
  3713.     CTFOpenAdminMenu(ent);
  3714. }
  3715.  
  3716. void CTFAdmin_SettingsCancel(edict_t *ent, pmenuhnd_t *p)
  3717. {
  3718.     admin_settings_t *settings = p->arg;
  3719.  
  3720.     PMenu_Close(ent);
  3721.     CTFOpenAdminMenu(ent);
  3722. }
  3723.  
  3724. void CTFAdmin_ChangeMatchLen(edict_t *ent, pmenuhnd_t *p)
  3725. {
  3726.     admin_settings_t *settings = p->arg;
  3727.  
  3728.     settings->matchlen = (settings->matchlen % 60) + 5;
  3729.     if (settings->matchlen < 5)
  3730.         settings->matchlen = 5;
  3731.  
  3732.     CTFAdmin_UpdateSettings(ent, p);
  3733. }
  3734.  
  3735. void CTFAdmin_ChangeMatchSetupLen(edict_t *ent, pmenuhnd_t *p)
  3736. {
  3737.     admin_settings_t *settings = p->arg;
  3738.  
  3739.     settings->matchsetuplen = (settings->matchsetuplen % 60) + 5;
  3740.     if (settings->matchsetuplen < 5)
  3741.         settings->matchsetuplen = 5;
  3742.  
  3743.     CTFAdmin_UpdateSettings(ent, p);
  3744. }
  3745.  
  3746. void CTFAdmin_ChangeMatchStartLen(edict_t *ent, pmenuhnd_t *p)
  3747. {
  3748.     admin_settings_t *settings = p->arg;
  3749.  
  3750.     settings->matchstartlen = (settings->matchstartlen % 600) + 10;
  3751.     if (settings->matchstartlen < 20)
  3752.         settings->matchstartlen = 20;
  3753.  
  3754.     CTFAdmin_UpdateSettings(ent, p);
  3755. }
  3756.  
  3757. void CTFAdmin_ChangeWeapStay(edict_t *ent, pmenuhnd_t *p)
  3758. {
  3759.     admin_settings_t *settings = p->arg;
  3760.  
  3761.     settings->weaponsstay = !settings->weaponsstay;
  3762.     CTFAdmin_UpdateSettings(ent, p);
  3763. }
  3764.  
  3765. void CTFAdmin_ChangeInstantItems(edict_t *ent, pmenuhnd_t *p)
  3766. {
  3767.     admin_settings_t *settings = p->arg;
  3768.  
  3769.     settings->instantitems = !settings->instantitems;
  3770.     CTFAdmin_UpdateSettings(ent, p);
  3771. }
  3772.  
  3773. void CTFAdmin_ChangeQuadDrop(edict_t *ent, pmenuhnd_t *p)
  3774. {
  3775.     admin_settings_t *settings = p->arg;
  3776.  
  3777.     settings->quaddrop = !settings->quaddrop;
  3778.     CTFAdmin_UpdateSettings(ent, p);
  3779. }
  3780.  
  3781. void CTFAdmin_ChangeInstantWeap(edict_t *ent, pmenuhnd_t *p)
  3782. {
  3783.     admin_settings_t *settings = p->arg;
  3784.  
  3785.     settings->instantweap = !settings->instantweap;
  3786.     CTFAdmin_UpdateSettings(ent, p);
  3787. }
  3788.  
  3789. void CTFAdmin_ChangeMatchLock(edict_t *ent, pmenuhnd_t *p)
  3790. {
  3791.     admin_settings_t *settings = p->arg;
  3792.  
  3793.     settings->matchlock = !settings->matchlock;
  3794.     CTFAdmin_UpdateSettings(ent, p);
  3795. }
  3796.  
  3797. void CTFAdmin_UpdateSettings(edict_t *ent, pmenuhnd_t *setmenu)
  3798. {
  3799.     int i = 2;
  3800.     char text[64];
  3801.     admin_settings_t *settings = setmenu->arg;
  3802.  
  3803.     sprintf(text, "Match Len:       %2d mins", settings->matchlen);
  3804.     PMenu_UpdateEntry(setmenu->entries + i, text, PMENU_ALIGN_LEFT, CTFAdmin_ChangeMatchLen);
  3805.     i++;
  3806.  
  3807.     sprintf(text, "Match Setup Len: %2d mins", settings->matchsetuplen);
  3808.     PMenu_UpdateEntry(setmenu->entries + i, text, PMENU_ALIGN_LEFT, CTFAdmin_ChangeMatchSetupLen);
  3809.     i++;
  3810.  
  3811.     sprintf(text, "Match Start Len: %2d secs", settings->matchstartlen);
  3812.     PMenu_UpdateEntry(setmenu->entries + i, text, PMENU_ALIGN_LEFT, CTFAdmin_ChangeMatchStartLen);
  3813.     i++;
  3814.  
  3815.     sprintf(text, "Weapons Stay:    %s", settings->weaponsstay ? "Yes" : "No");
  3816.     PMenu_UpdateEntry(setmenu->entries + i, text, PMENU_ALIGN_LEFT, CTFAdmin_ChangeWeapStay);
  3817.     i++;
  3818.  
  3819.     sprintf(text, "Instant Items:   %s", settings->instantitems ? "Yes" : "No");
  3820.     PMenu_UpdateEntry(setmenu->entries + i, text, PMENU_ALIGN_LEFT, CTFAdmin_ChangeInstantItems);
  3821.     i++;
  3822.  
  3823.     sprintf(text, "Quad Drop:       %s", settings->quaddrop ? "Yes" : "No");
  3824.     PMenu_UpdateEntry(setmenu->entries + i, text, PMENU_ALIGN_LEFT, CTFAdmin_ChangeQuadDrop);
  3825.     i++;
  3826.  
  3827.     sprintf(text, "Instant Weapons: %s", settings->instantweap ? "Yes" : "No");
  3828.     PMenu_UpdateEntry(setmenu->entries + i, text, PMENU_ALIGN_LEFT, CTFAdmin_ChangeInstantWeap);
  3829.     i++;
  3830.  
  3831.     sprintf(text, "Match Lock:      %s", settings->matchlock ? "Yes" : "No");
  3832.     PMenu_UpdateEntry(setmenu->entries + i, text, PMENU_ALIGN_LEFT, CTFAdmin_ChangeMatchLock);
  3833.     i++;
  3834.  
  3835.     PMenu_Update(ent);
  3836. }
  3837.  
  3838. pmenu_t def_setmenu[] = {
  3839.     { "*Settings Menu", PMENU_ALIGN_CENTER, NULL },
  3840.     { NULL,                PMENU_ALIGN_CENTER, NULL },
  3841.     { NULL,                PMENU_ALIGN_LEFT, NULL },    //int matchlen;         
  3842.     { NULL,                PMENU_ALIGN_LEFT, NULL },    //int matchsetuplen;    
  3843.     { NULL,                PMENU_ALIGN_LEFT, NULL },    //int matchstartlen;    
  3844.     { NULL,                PMENU_ALIGN_LEFT, NULL },    //qboolean weaponsstay; 
  3845.     { NULL,                PMENU_ALIGN_LEFT, NULL },    //qboolean instantitems;
  3846.     { NULL,                PMENU_ALIGN_LEFT, NULL },    //qboolean quaddrop;    
  3847.     { NULL,                PMENU_ALIGN_LEFT, NULL },    //qboolean instantweap; 
  3848.     { NULL,                PMENU_ALIGN_LEFT, NULL },    //qboolean matchlock; 
  3849.     { NULL,                PMENU_ALIGN_LEFT, NULL },
  3850.     { "Apply",            PMENU_ALIGN_LEFT, CTFAdmin_SettingsApply },
  3851.     { "Cancel",            PMENU_ALIGN_LEFT, CTFAdmin_SettingsCancel }
  3852. };
  3853.  
  3854. void CTFAdmin_Settings(edict_t *ent, pmenuhnd_t *p)
  3855. {
  3856.     admin_settings_t *settings;
  3857.     pmenuhnd_t *menu;
  3858.  
  3859.     PMenu_Close(ent);
  3860.  
  3861.     settings = malloc(sizeof(*settings));
  3862.  
  3863.     settings->matchlen = matchtime->value;
  3864.     settings->matchsetuplen = matchsetuptime->value;
  3865.     settings->matchstartlen = matchstarttime->value;
  3866.     settings->weaponsstay = !!((int)dmflags->value & DF_WEAPONS_STAY);
  3867.     settings->instantitems = !!((int)dmflags->value & DF_INSTANT_ITEMS);
  3868.     settings->quaddrop = !!((int)dmflags->value & DF_QUAD_DROP);
  3869.     settings->instantweap = instantweap->value != 0;
  3870.     settings->matchlock = matchlock->value != 0;
  3871.  
  3872.     menu = PMenu_Open(ent, def_setmenu, -1, sizeof(def_setmenu) / sizeof(pmenu_t), settings);
  3873.     CTFAdmin_UpdateSettings(ent, menu);
  3874. }
  3875.  
  3876. void CTFAdmin_MatchSet(edict_t *ent, pmenuhnd_t *p)
  3877. {
  3878.     PMenu_Close(ent);
  3879.  
  3880.     if (ctfgame.match == MATCH_SETUP) {
  3881.         gi.bprintf(PRINT_CHAT, "Match has been forced to start.\n");
  3882.         ctfgame.match = MATCH_PREGAME;
  3883.         ctfgame.matchtime = level.time + matchstarttime->value;
  3884.         gi.positioned_sound (world->s.origin, world, CHAN_AUTO | CHAN_RELIABLE, gi.soundindex("misc/talk1.wav"), 1, ATTN_NONE, 0);
  3885.         ctfgame.countdown = false;
  3886.     } else if (ctfgame.match == MATCH_GAME) {
  3887.         gi.bprintf(PRINT_CHAT, "Match has been forced to terminate.\n");
  3888.         ctfgame.match = MATCH_SETUP;
  3889.         ctfgame.matchtime = level.time + matchsetuptime->value * 60;
  3890.         CTFResetAllPlayers();
  3891.     }
  3892. }
  3893.  
  3894. void CTFAdmin_MatchMode(edict_t *ent, pmenuhnd_t *p)
  3895. {
  3896.     PMenu_Close(ent);
  3897.  
  3898.     if (ctfgame.match != MATCH_SETUP) {
  3899.         if (competition->value < 3)
  3900.             gi.cvar_set("competition", "2");
  3901.         ctfgame.match = MATCH_SETUP;
  3902.         CTFResetAllPlayers();
  3903.     }
  3904. }
  3905.  
  3906. void CTFAdmin_Reset(edict_t *ent, pmenuhnd_t *p)
  3907. {
  3908.     PMenu_Close(ent);
  3909.  
  3910.     // go back to normal mode
  3911.     gi.bprintf(PRINT_CHAT, "Match mode has been terminated, reseting to normal game.\n");
  3912.     ctfgame.match = MATCH_NONE;
  3913.     gi.cvar_set("competition", "1");
  3914.     CTFResetAllPlayers();
  3915. }
  3916.  
  3917. void CTFAdmin_Cancel(edict_t *ent, pmenuhnd_t *p)
  3918. {
  3919.     PMenu_Close(ent);
  3920. }
  3921.  
  3922.  
  3923. pmenu_t adminmenu[] = {
  3924.     { "*Administration Menu",    PMENU_ALIGN_CENTER, NULL },
  3925.     { NULL,                        PMENU_ALIGN_CENTER, NULL }, // blank
  3926.     { "Settings",                PMENU_ALIGN_LEFT, CTFAdmin_Settings },
  3927.     { NULL,                        PMENU_ALIGN_LEFT, NULL },
  3928.     { NULL,                        PMENU_ALIGN_LEFT, NULL },
  3929.     { "Cancel",                    PMENU_ALIGN_LEFT, CTFAdmin_Cancel },
  3930.     { NULL,                        PMENU_ALIGN_CENTER, NULL },
  3931. };
  3932.  
  3933. void CTFOpenAdminMenu(edict_t *ent)
  3934. {
  3935.     adminmenu[3].text = NULL;
  3936.     adminmenu[3].SelectFunc = NULL;
  3937.     adminmenu[4].text = NULL;
  3938.     adminmenu[4].SelectFunc = NULL;
  3939.     if (ctfgame.match == MATCH_SETUP) {
  3940.         adminmenu[3].text = "Force start match";
  3941.         adminmenu[3].SelectFunc = CTFAdmin_MatchSet;
  3942.         adminmenu[4].text = "Reset to pickup mode";
  3943.         adminmenu[4].SelectFunc = CTFAdmin_Reset;
  3944.     } else if (ctfgame.match == MATCH_GAME || ctfgame.match == MATCH_PREGAME) {
  3945.         adminmenu[3].text = "Cancel match";
  3946.         adminmenu[3].SelectFunc = CTFAdmin_MatchSet;
  3947.     } else if (ctfgame.match == MATCH_NONE && competition->value) {
  3948.         adminmenu[3].text = "Switch to match mode";
  3949.         adminmenu[3].SelectFunc = CTFAdmin_MatchMode;
  3950.     }
  3951.  
  3952.  
  3953. //    if (ent->client->menu)
  3954. //        PMenu_Close(ent->client->menu);
  3955.  
  3956.     PMenu_Open(ent, adminmenu, -1, sizeof(adminmenu) / sizeof(pmenu_t), NULL);
  3957. }
  3958.  
  3959. void CTFAdmin(edict_t *ent)
  3960. {
  3961.     char text[1024];
  3962.  
  3963.     if (!allow_admin->value) {
  3964.         gi.cprintf(ent, PRINT_HIGH, "Administration is disabled\n");
  3965.         return;
  3966.     }
  3967.  
  3968.     if (gi.argc() > 1 && admin_password->string && *admin_password->string &&
  3969.         !ent->client->resp.admin && strcmp(admin_password->string, gi.argv(1)) == 0) {
  3970.         ent->client->resp.admin = true;
  3971.         gi.bprintf(PRINT_HIGH, "%s has become an admin.\n", ent->client->pers.netname);
  3972.         gi.cprintf(ent, PRINT_HIGH, "Type 'admin' to access the adminstration menu.\n");
  3973.     }
  3974.  
  3975.     if (!ent->client->resp.admin) {
  3976.         sprintf(text, "%s has requested admin rights.",
  3977.             ent->client->pers.netname);
  3978.         CTFBeginElection(ent, ELECT_ADMIN, text);
  3979.         return;
  3980.     }
  3981.  
  3982.     if (ent->client->menu)
  3983.         PMenu_Close(ent);
  3984.  
  3985.     CTFOpenAdminMenu(ent);
  3986. }
  3987.  
  3988. /*----------------------------------------------------------------*/
  3989.  
  3990. void CTFStats(edict_t *ent)
  3991. {
  3992.     int i, e;
  3993.     ghost_t *g;
  3994.     char st[80];
  3995.     char text[1024];
  3996.     edict_t *e2;
  3997.  
  3998.     *text = 0;
  3999.     if (ctfgame.match == MATCH_SETUP) {
  4000.         for (i = 1; i <= maxclients->value; i++) {
  4001.             e2 = g_edicts + i;
  4002.             if (!e2->inuse)
  4003.                 continue;
  4004.             if (!e2->client->resp.ready && e2->client->resp.ctf_team != CTF_NOTEAM) {
  4005.                 sprintf(st, "%s is not ready.\n", e2->client->pers.netname);
  4006.                 if (strlen(text) + strlen(st) < sizeof(text) - 50)
  4007.                     strcat(text, st);
  4008.             }
  4009.         }
  4010.     }
  4011.  
  4012.     for (i = 0, g = ctfgame.ghosts; i < MAX_CLIENTS; i++, g++)
  4013.         if (g->ent)
  4014.             break;
  4015.  
  4016.     if (i == MAX_CLIENTS) {
  4017.         if (*text)
  4018.             gi.cprintf(ent, PRINT_HIGH, "%s", text);
  4019.         gi.cprintf(ent, PRINT_HIGH, "No statistics available.\n");
  4020.         return;
  4021.     }
  4022.  
  4023.     strcat(text, "  #|Name            |Score|Kills|Death|BasDf|CarDf|Effcy|\n");
  4024.  
  4025.     for (i = 0, g = ctfgame.ghosts; i < MAX_CLIENTS; i++, g++) {
  4026.         if (!*g->netname)
  4027.             continue;
  4028.  
  4029.         if (g->deaths + g->kills == 0)
  4030.             e = 50;
  4031.         else
  4032.             e = g->kills * 100 / (g->kills + g->deaths);
  4033.         sprintf(st, "%3d|%-16.16s|%5d|%5d|%5d|%5d|%5d|%4d%%|\n",
  4034.             g->number, 
  4035.             g->netname, 
  4036.             g->score, 
  4037.             g->kills, 
  4038.             g->deaths, 
  4039.             g->basedef,
  4040.             g->carrierdef, 
  4041.             e);
  4042.         if (strlen(text) + strlen(st) > sizeof(text) - 50) {
  4043.             sprintf(text+strlen(text), "And more...\n");
  4044.             gi.cprintf(ent, PRINT_HIGH, "%s", text);
  4045.             return;
  4046.         }
  4047.         strcat(text, st);
  4048.     }
  4049.     gi.cprintf(ent, PRINT_HIGH, "%s", text);
  4050. }
  4051.  
  4052. void CTFPlayerList(edict_t *ent)
  4053. {
  4054.     int i;
  4055.     char st[80];
  4056.     char text[1400];
  4057.     edict_t *e2;
  4058.  
  4059. #if 0
  4060.     *text = 0;
  4061.     if (ctfgame.match == MATCH_SETUP) {
  4062.         for (i = 1; i <= maxclients->value; i++) {
  4063.             e2 = g_edicts + i;
  4064.             if (!e2->inuse)
  4065.                 continue;
  4066.             if (!e2->client->resp.ready && e2->client->resp.ctf_team != CTF_NOTEAM) {
  4067.                 sprintf(st, "%s is not ready.\n", e2->client->pers.netname);
  4068.                 if (strlen(text) + strlen(st) < sizeof(text) - 50)
  4069.                     strcat(text, st);
  4070.             }
  4071.         }
  4072.     }
  4073. #endif
  4074.  
  4075.     // number, name, connect time, ping, score, admin
  4076.  
  4077.     *text = 0;
  4078.     for (i = 1; i <= maxclients->value; i++) {
  4079.         e2 = g_edicts + i;
  4080.         if (!e2->inuse)
  4081.             continue;
  4082.  
  4083.         Com_sprintf(st, sizeof(st), "%3d %-16.16s %02d:%02d %4d %3d%s%s\n",
  4084.             i,
  4085.             e2->client->pers.netname,
  4086.             (level.framenum - e2->client->resp.enterframe) / 600,
  4087.             ((level.framenum - e2->client->resp.enterframe) % 600)/10,
  4088.             e2->client->ping,
  4089.             e2->client->resp.score,
  4090.             (ctfgame.match == MATCH_SETUP || ctfgame.match == MATCH_PREGAME) ?
  4091.             (e2->client->resp.ready ? " (ready)" : " (notready)") : "",
  4092.             e2->client->resp.admin ? " (admin)" : "");
  4093.  
  4094.         if (strlen(text) + strlen(st) > sizeof(text) - 50) {
  4095.             sprintf(text+strlen(text), "And more...\n");
  4096.             gi.cprintf(ent, PRINT_HIGH, "%s", text);
  4097.             return;
  4098.         }
  4099.         strcat(text, st);
  4100.     }
  4101.     gi.cprintf(ent, PRINT_HIGH, "%s", text);
  4102. }
  4103.  
  4104.  
  4105. void CTFWarp(edict_t *ent)
  4106. {
  4107.     char text[1024];
  4108.     char *mlist, *token;
  4109.     static const char *seps = " \t\n\r";
  4110.  
  4111.     if (gi.argc() < 2) {
  4112.         gi.cprintf(ent, PRINT_HIGH, "Where do you want to warp to?\n");
  4113.         gi.cprintf(ent, PRINT_HIGH, "Available levels are: %s\n", warp_list->string);
  4114.         return;
  4115.     }
  4116.  
  4117.     mlist = strdup(warp_list->string);
  4118.  
  4119.     token = strtok(mlist, seps);
  4120.     while (token != NULL) {
  4121.         if (Q_stricmp(token, gi.argv(1)) == 0)
  4122.             break;
  4123.         token = strtok(NULL, seps);
  4124.     }
  4125.  
  4126.     if (token == NULL) {
  4127.         gi.cprintf(ent, PRINT_HIGH, "Unknown CTF level.\n");
  4128.         gi.cprintf(ent, PRINT_HIGH, "Available levels are: %s\n", warp_list->string);
  4129.         free(mlist);
  4130.         return;
  4131.     }
  4132.  
  4133.     free(mlist);
  4134.  
  4135.  
  4136.     if (ent->client->resp.admin) {
  4137.         gi.bprintf(PRINT_HIGH, "%s is warping to level %s.\n", 
  4138.             ent->client->pers.netname, gi.argv(1));
  4139.         strncpy(level.forcemap, gi.argv(1), sizeof(level.forcemap) - 1);
  4140.         EndDMLevel();
  4141.         return;
  4142.     }
  4143.  
  4144.     sprintf(text, "%s has requested warping to level %s.", 
  4145.             ent->client->pers.netname, gi.argv(1));
  4146.     if (CTFBeginElection(ent, ELECT_MAP, text))
  4147.         strncpy(ctfgame.elevel, gi.argv(1), sizeof(ctfgame.elevel) - 1);
  4148. }
  4149.  
  4150. void CTFBoot(edict_t *ent)
  4151. {
  4152.     int i;
  4153.     edict_t *targ;
  4154.     char text[80];
  4155.  
  4156.     if (!ent->client->resp.admin) {
  4157.         gi.cprintf(ent, PRINT_HIGH, "You are not an admin.\n");
  4158.         return;
  4159.     }
  4160.  
  4161.     if (gi.argc() < 2) {
  4162.         gi.cprintf(ent, PRINT_HIGH, "Who do you want to kick?\n");
  4163.         return;
  4164.     }
  4165.  
  4166.     if (*gi.argv(1) < '0' && *gi.argv(1) > '9') {
  4167.         gi.cprintf(ent, PRINT_HIGH, "Specify the player number to kick.\n");
  4168.         return;
  4169.     }
  4170.  
  4171.     i = atoi(gi.argv(1));
  4172.     if (i < 1 || i > maxclients->value) {
  4173.         gi.cprintf(ent, PRINT_HIGH, "Invalid player number.\n");
  4174.         return;
  4175.     }
  4176.  
  4177.     targ = g_edicts + i;
  4178.     if (!targ->inuse) {
  4179.         gi.cprintf(ent, PRINT_HIGH, "That player number is not connected.\n");
  4180.         return;
  4181.     }
  4182.  
  4183.     sprintf(text, "kick %d\n", i - 1);
  4184.     gi.AddCommandString(text);
  4185. }
  4186.  
  4187.  
  4188. void CTFSetPowerUpEffect(edict_t *ent, int def)
  4189. {
  4190.     if (ent->client->resp.ctf_team == CTF_TEAM1)
  4191.         ent->s.effects |= EF_PENT; // red
  4192.     else if (ent->client->resp.ctf_team == CTF_TEAM2)
  4193.         ent->s.effects |= EF_QUAD; // red
  4194.     else
  4195.         ent->s.effects |= def;
  4196. }
  4197.  
  4198.